@doswiftly/storefront-operations 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/AGENTS.md +2 -2
- package/CHANGELOG.md +244 -0
- package/README.md +14 -13
- package/fragments.graphql +41 -15
- package/llms-full.txt +156 -34
- package/operations.json +56 -32
- package/package.json +1 -1
- package/queries.graphql +31 -12
- package/schema.graphql +165 -78
package/AGENTS.md
CHANGED
|
@@ -27,8 +27,8 @@ 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**:
|
|
31
|
-
- **Queries**:
|
|
30
|
+
- **Schema version**: 14.0.0
|
|
31
|
+
- **Queries**: 50
|
|
32
32
|
- **Mutations**: 40
|
|
33
33
|
- **Fragments**: 100
|
|
34
34
|
<!-- AUTOGEN:STATS:END -->
|
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
|
package/README.md
CHANGED
|
@@ -168,7 +168,7 @@ full executable body of each operation.
|
|
|
168
168
|
|
|
169
169
|
| Operation | Description |
|
|
170
170
|
| --- | --- |
|
|
171
|
-
| `Product` | Fetches a single product by `id` or `handle` (URL
|
|
171
|
+
| `Product` | Fetches a single product by `id` or `handle` (URL-friendly identifier). Pass either — whichever is provided wins; if both are missing, returns null. Returns null if the product is not storefront-accessible (must be `ACTIVE` status with `PUBLIC` or `BUNDLE_ONLY` visibility). |
|
|
172
172
|
| `ProductConfigurator` | Fetches a product together with its filtered attribute definitions, optimized for the configurator UI (e.g. customer-facing text fields, finishing options, scoped variants). `fillingMode: "CUSTOMER"` returns only customer-facing attributes; pass `"BOTH"` to also include attributes shared with the merchant admin. Single round-trip — saves a separate `attributes` query. |
|
|
173
173
|
| `Products` | Paginated product list (Relay Connection, default page size 20, max 100). The `query` argument supports a structured search syntax — `tag:summer`, `vendor:foo`, `product_type:shirts`, `variants.price:>10`, plus `AND`/`OR`/`NOT` — falling back to free-text title/content search. The `filters[]` array uses multi-filter logic: same field name appears multiple times → OR; different fields → AND. Sort: `RELEVANCE`, `TITLE`, `PRICE`, `NEWEST`, `OLDEST`, `BEST_SELLING`. The response includes a `filters` block for faceted navigation (counts per filterable attribute value). |
|
|
174
174
|
| `ProductSearch` | Full-text product search — `$query` is required. Functionally equivalent to `Products` with `$query` set, minus the `sortKey` argument (search defaults to relevance ranking). Use for the search results page; combine with `filters[]` for guided refinement. |
|
|
@@ -185,7 +185,7 @@ full executable body of each operation.
|
|
|
185
185
|
|
|
186
186
|
| Operation | Description |
|
|
187
187
|
| --- | --- |
|
|
188
|
-
| `Category` | Fetches a single category by `id` or `
|
|
188
|
+
| `Category` | Fetches a single category by `id` or `handle` with its parent and immediate children. Use for breadcrumbs and sub-navigation. Nested queries on `parent` / `children` are batched server-side — safe to use in lists without N+1 concerns. |
|
|
189
189
|
| `Categories` | Returns active root categories for the shop. Each root exposes its `children` — build the tree client-side by walking those fields (server batches the lookups, no N+1). The hierarchy is not depth-capped server-side. Use for nav mega-menus and category pages. |
|
|
190
190
|
|
|
191
191
|
#### Cart
|
|
@@ -241,7 +241,8 @@ full executable body of each operation.
|
|
|
241
241
|
|
|
242
242
|
| Operation | Description |
|
|
243
243
|
| --- | --- |
|
|
244
|
-
| `AvailableShippingMethods` | Returns shipping methods for a given destination address and cart shape (subtotal, total weight, currency). The query computes everything from the inputs alone — no existing cart is required, so it can be used for "shipping cost preview" UIs before the customer adds anything to a cart. Each method includes price, free-shipping progress (`{ qualifies, currentAmount, threshold, remaining, progressPercent }`), estimated delivery, and carrier metadata. Sorted by the merchant's `sortOrder`, then by price. |
|
|
244
|
+
| `AvailableShippingMethods` | Returns shipping methods for a given destination address and cart shape (subtotal, total weight, currency). The query computes everything from the inputs alone — no existing cart is required, so it can be used for "shipping cost preview" UIs (e.g. product detail page shipping calculator) before the customer adds anything to a cart. Each method includes price, free-shipping progress (`{ qualifies, currentAmount, threshold, remaining, progressPercent }`), estimated delivery, and carrier metadata. Sorted by the merchant's `sortOrder`, then by price. For a cart-bound checkout flow (where the cart is already known and the storefront wants the resolver to skip non-physical items and surface a `DIGITAL_ONLY_NO_SHIPPING` user error for all-digital carts), use `CartAvailableShippingMethods` against `cart.availableShippingMethods(address)` instead. |
|
|
245
|
+
| `CartAvailableShippingMethods` | Cart-aware shipping methods discovery. Returns shipping methods available for the cart's contents at the given destination, with subtotal and physical-item weight pulled from the cart aggregate (no need to compute them client-side). When the cart contains only non-physical items (digital, gift card, service, subscription), the response is `methods: []` plus a `DIGITAL_ONLY_NO_SHIPPING` user error — use this as the signal to skip rendering the shipping picker step. Prefer this query over the standalone `AvailableShippingMethods` once a cart has been created (`cartCreate`). For pre-cart "shipping cost preview" UIs on product detail pages, the standalone query remains the right tool. |
|
|
245
246
|
|
|
246
247
|
#### Attribute Filters
|
|
247
248
|
|
|
@@ -279,8 +280,8 @@ full executable body of each operation.
|
|
|
279
280
|
|
|
280
281
|
| Operation | Description |
|
|
281
282
|
| --- | --- |
|
|
282
|
-
| `BlogPosts` | Paginated list of published blog posts. Filter by `
|
|
283
|
-
| `BlogPost` | Fetches a single blog post by `id` or `
|
|
283
|
+
| `BlogPosts` | Paginated list of published blog posts. Filter by `categoryHandle`, `tagHandle`, or `featured` (boolean flag, not enum). Sort: `PUBLISHED_AT` (default), `TITLE`, `VIEW_COUNT`, or `CREATED_AT`. Public; no auth required. |
|
|
284
|
+
| `BlogPost` | Fetches a single blog post by `id` or `handle`. Visibility-gated: returns null if the post is not yet `PUBLISHED` or if its publish date is in the future (scheduled posts stay hidden until their publish time). Side effect: fetching a post increments its `view_count` asynchronously (does not block the response). |
|
|
284
285
|
| `BlogCategories` | Lists all blog categories with per-category `postCount` and SEO metadata. Use to render category navigation on blog pages. Public; no auth required. |
|
|
285
286
|
| `BlogTags` | Lists blog tags with usage counts (`postCount` per tag). Use to render a tag cloud. Public; no auth required. |
|
|
286
287
|
|
|
@@ -301,7 +302,7 @@ full executable body of each operation.
|
|
|
301
302
|
|
|
302
303
|
| Operation | Description |
|
|
303
304
|
| --- | --- |
|
|
304
|
-
| `Menu` | Fetches a navigation menu by `handle` (e.g. `"main-menu"`, `"footer"`, `"mobile"`). Returns the nested item tree. Each item is typed as one of: `HTTP`, `FRONTPAGE`, `SEARCH`, `CATALOG`, `BLOG`, `PRODUCT`, `COLLECTION`, `CATEGORY`, or `
|
|
305
|
+
| `Menu` | Fetches a navigation menu by `handle` (e.g. `"main-menu"`, `"footer"`, `"mobile"`). Returns the nested item tree (up to 3 levels). Each item is typed as one of: `HTTP`, `FRONTPAGE`, `SEARCH`, `CATALOG`, `BLOG`, `PRODUCT`, `COLLECTION`, `CATEGORY`, `PAGE`, or `BRAND` — switch on the type to render the right link target. Each resource-linked item exposes both a pre-resolved `url` (standard `/categories\|/collections\|/pages\|/products\|/brands/<handle>` convention) and a typed `resource` union with the raw handle so storefronts with custom routing can construct their own paths instead. All resource lookups are batched per request — no N+1 even for deep menus. |
|
|
305
306
|
|
|
306
307
|
#### Content: URL Redirects
|
|
307
308
|
|
|
@@ -458,7 +459,7 @@ full executable body of each operation.
|
|
|
458
459
|
|
|
459
460
|
| Fragment | On Type | Description |
|
|
460
461
|
| --- | --- | --- |
|
|
461
|
-
| `Category` | `Category` | Category node in the shop's taxonomy — name,
|
|
462
|
+
| `Category` | `Category` | Category node in the shop's taxonomy — name, handle, hero image, depth `level`, materialized `path`, product count, sort order. Spread on category cards and breadcrumb segments. Does NOT include parent / children — query those fields separately and let the server batch them. |
|
|
462
463
|
|
|
463
464
|
#### Customer
|
|
464
465
|
|
|
@@ -553,7 +554,7 @@ full executable body of each operation.
|
|
|
553
554
|
| `AttributeFilterValue` | `AttributeFilterValue` | One discrete value in a filterable attribute (e.g. "Red" for the Color filter). Includes `productCount` in the current listing context (for "(12)" badges next to filter values), optional swatch and price modifier. |
|
|
554
555
|
| `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
556
|
| `PriceRangeFilter` | `PriceRange` | Min / max price range across products in the current listing context. Use to bound the price slider. |
|
|
556
|
-
| `CategoryFilterOption` | `CategoryFilterOption` | One category option in the categories filter — id, name,
|
|
557
|
+
| `CategoryFilterOption` | `CategoryFilterOption` | One category option in the categories filter — id, name, handle, count of products in this category within the current listing context, level + parentId for tree rendering. |
|
|
557
558
|
| `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
559
|
|
|
559
560
|
#### Loyalty Program
|
|
@@ -592,9 +593,9 @@ full executable body of each operation.
|
|
|
592
593
|
|
|
593
594
|
| Fragment | On Type | Description |
|
|
594
595
|
| --- | --- | --- |
|
|
595
|
-
| `BlogCategory` | `BlogCategory` | Blog category — name,
|
|
596
|
-
| `BlogTag` | `BlogTag` | Blog tag — name,
|
|
597
|
-
| `BlogPost` | `BlogPost` | Full blog post — title,
|
|
596
|
+
| `BlogCategory` | `BlogCategory` | Blog category — name, handle, description, post count. Spread on category navigation and post category labels. |
|
|
597
|
+
| `BlogTag` | `BlogTag` | Blog tag — name, handle, post count. Spread on tag clouds and post tag labels. |
|
|
598
|
+
| `BlogPost` | `BlogPost` | Full blog post — title, handle, excerpt, content (with `contentFormat` indicating HTML/Markdown), featured image, author, category, tags, publish date, reading time, view + comment counts, SEO metadata. Spread on the post detail page. |
|
|
598
599
|
|
|
599
600
|
#### Store Availability (BOPIS / multi-location)
|
|
600
601
|
|
|
@@ -612,13 +613,13 @@ full executable body of each operation.
|
|
|
612
613
|
|
|
613
614
|
| Fragment | On Type | Description |
|
|
614
615
|
| --- | --- | --- |
|
|
615
|
-
| `ShopPage` | `ShopPage` | CMS page — handle (URL
|
|
616
|
+
| `ShopPage` | `ShopPage` | CMS page — handle (URL-friendly identifier), title, body (HTML), excerpt, SEO metadata, publish date. Spread on About / Privacy / Terms / Returns Policy pages. |
|
|
616
617
|
|
|
617
618
|
#### Navigation Menus
|
|
618
619
|
|
|
619
620
|
| Fragment | On Type | Description |
|
|
620
621
|
| --- | --- | --- |
|
|
621
|
-
| `MenuItem` | `MenuItem` | One menu item with up to 3 levels of nested children — title, URL, type (`HTTP` / `FRONTPAGE` / `SEARCH` / `CATALOG` / `BLOG` / `PRODUCT` / `COLLECTION` / `CATEGORY` / `PAGE`), `resourceId` for typed items, optional image. Switch on `type` to
|
|
622
|
+
| `MenuItem` | `MenuItem` | One menu item with up to 3 levels of nested children — title, URL, type (`HTTP` / `FRONTPAGE` / `SEARCH` / `CATALOG` / `BLOG` / `PRODUCT` / `COLLECTION` / `CATEGORY` / `PAGE` / `BRAND`), `resourceId` for typed items, optional image, and a typed `resource` union (Category / Collection / ShopPage / Product / Brand) carrying the linked resource's handle. Two ways to render the link target: (1) use the pre-resolved `url` if your storefront follows the standard `/categories\|/collections\|/pages\|/products\|/brands/<handle>` route convention; (2) read `resource.__typename` + the per-type `handle` and build your own paths if your storefront uses different routing. Switch on `type` to decide which static (FRONTPAGE/SEARCH/CATALOG/BLOG) or dynamic target to render. |
|
|
622
623
|
| `Menu` | `Menu` | Navigation menu — handle (e.g. "main-menu", "footer", "mobile"), title, nested item tree. Returned by `menu($handle)` query. |
|
|
623
624
|
|
|
624
625
|
#### URL Redirects
|
package/fragments.graphql
CHANGED
|
@@ -119,7 +119,7 @@ fragment ProductCard on Product {
|
|
|
119
119
|
vendor
|
|
120
120
|
categories {
|
|
121
121
|
id
|
|
122
|
-
|
|
122
|
+
handle
|
|
123
123
|
name
|
|
124
124
|
}
|
|
125
125
|
isAvailable
|
|
@@ -225,11 +225,11 @@ fragment Collection on Collection {
|
|
|
225
225
|
# Categories
|
|
226
226
|
# ============================================
|
|
227
227
|
|
|
228
|
-
# Category node in the shop's taxonomy — name,
|
|
228
|
+
# Category node in the shop's taxonomy — name, handle, hero image, depth `level`, materialized `path`, product count, sort order. Spread on category cards and breadcrumb segments. Does NOT include parent / children — query those fields separately and let the server batch them.
|
|
229
229
|
fragment Category on Category {
|
|
230
230
|
id
|
|
231
231
|
name
|
|
232
|
-
|
|
232
|
+
handle
|
|
233
233
|
description
|
|
234
234
|
image {
|
|
235
235
|
...ImageCard
|
|
@@ -407,6 +407,7 @@ fragment CartLine on CartLine {
|
|
|
407
407
|
productTitle
|
|
408
408
|
productHandle
|
|
409
409
|
productType
|
|
410
|
+
requiresShipping
|
|
410
411
|
}
|
|
411
412
|
|
|
412
413
|
# Buyer identity associated with the cart. Note: only `customerId` is currently persisted server-side; `email`, `phone`, `countryCode` are accepted in the input but ignored.
|
|
@@ -488,6 +489,7 @@ fragment Cart on Cart {
|
|
|
488
489
|
appliedGiftCards {
|
|
489
490
|
...CartAppliedGiftCard
|
|
490
491
|
}
|
|
492
|
+
requiresShipping
|
|
491
493
|
createdAt
|
|
492
494
|
updatedAt
|
|
493
495
|
}
|
|
@@ -1002,11 +1004,11 @@ fragment PriceRangeFilter on PriceRange {
|
|
|
1002
1004
|
}
|
|
1003
1005
|
}
|
|
1004
1006
|
|
|
1005
|
-
# One category option in the categories filter — id, name,
|
|
1007
|
+
# One category option in the categories filter — id, name, handle, count of products in this category within the current listing context, level + parentId for tree rendering.
|
|
1006
1008
|
fragment CategoryFilterOption on CategoryFilterOption {
|
|
1007
1009
|
id
|
|
1008
1010
|
name
|
|
1009
|
-
|
|
1011
|
+
handle
|
|
1010
1012
|
productCount
|
|
1011
1013
|
level
|
|
1012
1014
|
parentId
|
|
@@ -1119,7 +1121,7 @@ fragment LoyaltyTransaction on LoyaltyTransaction {
|
|
|
1119
1121
|
fragment LoyaltyReward on LoyaltyReward {
|
|
1120
1122
|
id
|
|
1121
1123
|
name
|
|
1122
|
-
|
|
1124
|
+
handle
|
|
1123
1125
|
type
|
|
1124
1126
|
pointsCost
|
|
1125
1127
|
discountPercent
|
|
@@ -1269,28 +1271,28 @@ fragment Wishlist on Wishlist {
|
|
|
1269
1271
|
# Blog
|
|
1270
1272
|
# ============================================
|
|
1271
1273
|
|
|
1272
|
-
# Blog category — name,
|
|
1274
|
+
# Blog category — name, handle, description, post count. Spread on category navigation and post category labels.
|
|
1273
1275
|
fragment BlogCategory on BlogCategory {
|
|
1274
1276
|
id
|
|
1275
1277
|
name
|
|
1276
|
-
|
|
1278
|
+
handle
|
|
1277
1279
|
description
|
|
1278
1280
|
postCount
|
|
1279
1281
|
}
|
|
1280
1282
|
|
|
1281
|
-
# Blog tag — name,
|
|
1283
|
+
# Blog tag — name, handle, post count. Spread on tag clouds and post tag labels.
|
|
1282
1284
|
fragment BlogTag on BlogTag {
|
|
1283
1285
|
id
|
|
1284
1286
|
name
|
|
1285
|
-
|
|
1287
|
+
handle
|
|
1286
1288
|
postCount
|
|
1287
1289
|
}
|
|
1288
1290
|
|
|
1289
|
-
# Full blog post — title,
|
|
1291
|
+
# Full blog post — title, handle, excerpt, content (with `contentFormat` indicating HTML/Markdown), featured image, author, category, tags, publish date, reading time, view + comment counts, SEO metadata. Spread on the post detail page.
|
|
1290
1292
|
fragment BlogPost on BlogPost {
|
|
1291
1293
|
id
|
|
1292
1294
|
title
|
|
1293
|
-
|
|
1295
|
+
handle
|
|
1294
1296
|
excerpt
|
|
1295
1297
|
content
|
|
1296
1298
|
contentFormat
|
|
@@ -1425,7 +1427,7 @@ fragment VariantStoreAvailability on ProductVariant {
|
|
|
1425
1427
|
# Pages
|
|
1426
1428
|
# ============================================
|
|
1427
1429
|
|
|
1428
|
-
# CMS page — handle (URL
|
|
1430
|
+
# CMS page — handle (URL-friendly identifier), title, body (HTML), excerpt, SEO metadata, publish date. Spread on About / Privacy / Terms / Returns Policy pages.
|
|
1429
1431
|
fragment ShopPage on ShopPage {
|
|
1430
1432
|
id
|
|
1431
1433
|
handle
|
|
@@ -1445,13 +1447,21 @@ fragment ShopPage on ShopPage {
|
|
|
1445
1447
|
# Navigation Menus
|
|
1446
1448
|
# ============================================
|
|
1447
1449
|
|
|
1448
|
-
# One menu item with up to 3 levels of nested children — title, URL, type (`HTTP` / `FRONTPAGE` / `SEARCH` / `CATALOG` / `BLOG` / `PRODUCT` / `COLLECTION` / `CATEGORY` / `PAGE`), `resourceId` for typed items, optional image. Switch on `type` to
|
|
1450
|
+
# One menu item with up to 3 levels of nested children — title, URL, type (`HTTP` / `FRONTPAGE` / `SEARCH` / `CATALOG` / `BLOG` / `PRODUCT` / `COLLECTION` / `CATEGORY` / `PAGE` / `BRAND`), `resourceId` for typed items, optional image, and a typed `resource` union (Category / Collection / ShopPage / Product / Brand) carrying the linked resource's handle. Two ways to render the link target: (1) use the pre-resolved `url` if your storefront follows the standard `/categories|/collections|/pages|/products|/brands/<handle>` route convention; (2) read `resource.__typename` + the per-type `handle` and build your own paths if your storefront uses different routing. Switch on `type` to decide which static (FRONTPAGE/SEARCH/CATALOG/BLOG) or dynamic target to render.
|
|
1449
1451
|
fragment MenuItem on MenuItem {
|
|
1450
1452
|
id
|
|
1451
1453
|
title
|
|
1452
1454
|
url
|
|
1453
1455
|
type
|
|
1454
1456
|
resourceId
|
|
1457
|
+
resource {
|
|
1458
|
+
__typename
|
|
1459
|
+
... on Category { id handle name }
|
|
1460
|
+
... on Collection { id handle title }
|
|
1461
|
+
... on ShopPage { id handle title }
|
|
1462
|
+
... on Product { id handle title }
|
|
1463
|
+
... on Brand { id handle name logo }
|
|
1464
|
+
}
|
|
1455
1465
|
image {
|
|
1456
1466
|
...Image
|
|
1457
1467
|
}
|
|
@@ -1461,12 +1471,28 @@ fragment MenuItem on MenuItem {
|
|
|
1461
1471
|
url
|
|
1462
1472
|
type
|
|
1463
1473
|
resourceId
|
|
1474
|
+
resource {
|
|
1475
|
+
__typename
|
|
1476
|
+
... on Category { id handle name }
|
|
1477
|
+
... on Collection { id handle title }
|
|
1478
|
+
... on ShopPage { id handle title }
|
|
1479
|
+
... on Product { id handle title }
|
|
1480
|
+
... on Brand { id handle name logo }
|
|
1481
|
+
}
|
|
1464
1482
|
items {
|
|
1465
1483
|
id
|
|
1466
1484
|
title
|
|
1467
1485
|
url
|
|
1468
1486
|
type
|
|
1469
1487
|
resourceId
|
|
1488
|
+
resource {
|
|
1489
|
+
__typename
|
|
1490
|
+
... on Category { id handle name }
|
|
1491
|
+
... on Collection { id handle title }
|
|
1492
|
+
... on ShopPage { id handle title }
|
|
1493
|
+
... on Product { id handle title }
|
|
1494
|
+
... on Brand { id handle name logo }
|
|
1495
|
+
}
|
|
1470
1496
|
}
|
|
1471
1497
|
}
|
|
1472
1498
|
}
|
|
@@ -1526,7 +1552,7 @@ fragment ProductAttributeOption on ProductAttributeOption {
|
|
|
1526
1552
|
fragment ProductAttributeDefinition on ProductAttributeDefinition {
|
|
1527
1553
|
id
|
|
1528
1554
|
name
|
|
1529
|
-
|
|
1555
|
+
handle
|
|
1530
1556
|
description
|
|
1531
1557
|
type
|
|
1532
1558
|
fillingMode
|