@doswiftly/storefront-operations 15.1.0 → 16.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -27,10 +27,10 @@ consumer's `codegen.ts` references this package's `.graphql` files as
27
27
  live in the consumer's repo.
28
28
 
29
29
  <!-- AUTOGEN:STATS:BEGIN — auto-regenerated, do not edit by hand -->
30
- - **Schema version**: 15.1.0
31
- - **Queries**: 51
30
+ - **Schema version**: 16.0.0
31
+ - **Queries**: 52
32
32
  - **Mutations**: 40
33
- - **Fragments**: 101
33
+ - **Fragments**: 102
34
34
  <!-- AUTOGEN:STATS:END -->
35
35
 
36
36
  ## Loading order
@@ -174,6 +174,29 @@ semantics). Idempotency is the caller's responsibility. If you need retries
174
174
  on a mutation, wrap with your own backoff logic and check `userErrors` to
175
175
  decide whether to retry.
176
176
 
177
+ ### Configuration sources — `doswiftly.config.ts` first, env vars only as fallback
178
+
179
+ `@doswiftly/storefront-sdk` takes `apiUrl` and `shopSlug` as **explicit
180
+ `config`** on `createStorefrontClient` / `<StorefrontProvider>`. The SDK does
181
+ not read env vars, sniff hostnames, or inspect request headers — the
182
+ storefront supplies the values.
183
+
184
+ Scaffolded storefronts (`doswiftly init`) ship a config helper
185
+ (`lib/graphql/config.ts`) that walks three sources in order:
186
+
187
+ 1. **`doswiftly.config.ts`** — preferred. Committed config file generated at
188
+ scaffolding. No env wiring needed in normal use.
189
+ 2. **`NEXT_PUBLIC_API_URL` + `NEXT_PUBLIC_SHOP_SLUG`** — fallback for local
190
+ dev or storefronts scaffolded outside `doswiftly init`. `doswiftly dev`
191
+ rewrites `NEXT_PUBLIC_API_URL` at runtime to point at a local CORS proxy.
192
+ 3. **`http://localhost:8000` + `demo-shop`** — last-resort defaults.
193
+
194
+ If you generate code that falls back to env vars, use **exactly these names**.
195
+ **Do NOT invent alternatives** like `API_URL`, `STOREFRONT_URL`,
196
+ `TENANT_SLUG` — `doswiftly dev` keys off the canonical names; alternatives
197
+ mean the proxy starts but the storefront still calls the production API
198
+ directly, and CORS errors only surface on the first client-side mutation.
199
+
177
200
  ## When in doubt
178
201
 
179
202
  - **Schema question** ("what fields are on `X`?") → grep `schema.graphql`
package/CHANGELOG.md CHANGED
@@ -1,5 +1,244 @@
1
1
  # Changelog
2
2
 
3
+ ## 16.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - ca67b92: **BREAKING** — the formatting utilities switch from a hardcoded 13-currency map to runtime-driven `Intl.NumberFormat` resolution. Every ISO 4217 currency known to the runtime is now supported, with the locale-correct symbol and separators.
8
+
9
+ **API changes**
10
+ - `formatDate(date, locale)` — `locale` is now a required argument. The previous hardcoded `'en-US'` default is gone.
11
+ - `formatDateTime(date, locale)` — `locale` required.
12
+ - `formatNumber(num, locale)` — `locale` required.
13
+ - `formatPrice(price, locale?)` — `locale` is now optional. When omitted, the runtime default (`Intl.NumberFormat().resolvedOptions().locale`) is used. The previous `CURRENCY_LOCALES`-keyed auto-selection is gone.
14
+ - `formatAmount(amount, currencyCode, locale?)` — third argument added (optional).
15
+ - `formatPriceRange(min, max, locale?)` — third argument added (optional).
16
+ - `getCurrencySymbol(code, locale?)` — second argument added (optional). The symbol is derived from `Intl.NumberFormat.formatToParts()`; output depends on the locale (`getCurrencySymbol('PLN', 'pl-PL') === 'zł'`, `getCurrencySymbol('PLN', 'en-US') === 'PLN'`).
17
+
18
+ **Removed exports**
19
+ - `CURRENCY_SYMBOLS` — the 13-entry map is gone. Use `getCurrencySymbol(code, locale)` instead, or read the symbol from `Intl.NumberFormat.formatToParts()` directly.
20
+ - `CURRENCY_LOCALES` — the locale-to-currency map is gone. Pick the locale from the storefront's i18n context (or read `shop.localeToCurrencyMap` from the API for per-shop overrides) and pass it to `formatPrice` / `formatAmount`.
21
+
22
+ **Why**
23
+ - The hardcoded 13-currency map covered ~7% of the ISO 4217 currencies the API now ships in `CurrencyCode` (~180 entries). Every currency outside that list silently fell back to `en-US` formatting — a Brazilian (BRL) or Indian (INR) storefront had to either write its own formatter or accept US-style output.
24
+ - Locale↔currency is a **per-shop** decision (`shop.localeToCurrencyMap` in the schema is explicitly marked SSOT). A globally-hardcoded map in the SDK is the wrong place for that knowledge — it makes Polish customers of a multi-locale shop unable to see PLN in `'en-PL'` even when the merchant configured it.
25
+ - Money precision: `formatPrice` and `formatAmount` no longer apply `parseFloat` to `Money.amount`. Decimal strings are forwarded to `Intl.NumberFormat.format()` verbatim, preserving precision across locales and unusual subunit currencies (JPY/KRW zero-decimal, BHD/JOD/KWD three-decimal, ISK rounding).
26
+
27
+ **Migration**
28
+
29
+ ```ts
30
+ // Before
31
+ import {
32
+ formatDate,
33
+ formatPrice,
34
+ CURRENCY_LOCALES,
35
+ CURRENCY_SYMBOLS,
36
+ } from "@doswiftly/storefront-sdk";
37
+
38
+ formatDate(order.createdAt); // hardcoded "Dec 9, 2025"
39
+ formatPrice({ amount: "12.50", currencyCode: "PLN" }); // auto-picked pl-PL → "12,50 zł"
40
+ CURRENCY_LOCALES.PLN; // "pl-PL"
41
+ CURRENCY_SYMBOLS.PLN; // "zł"
42
+
43
+ // After (Client Components)
44
+ import { useLocale } from "next-intl";
45
+ import {
46
+ formatDate,
47
+ formatPrice,
48
+ getCurrencySymbol,
49
+ } from "@doswiftly/storefront-sdk";
50
+
51
+ const locale = useLocale(); // e.g. "pl-PL"
52
+ formatDate(order.createdAt, locale);
53
+ formatPrice({ amount: "12.50", currencyCode: "PLN" }, locale);
54
+ getCurrencySymbol("PLN", locale); // "zł"
55
+
56
+ // After (Server Components / Route Handlers)
57
+ import { getLocale } from "next-intl/server";
58
+ const locale = await getLocale();
59
+ formatDate(order.createdAt, locale);
60
+ ```
61
+
62
+ For per-shop locale↔currency overrides, read `shop.localeToCurrencyMap` from the Storefront API and resolve the locale yourself before calling `formatPrice` / `formatAmount`.
63
+
64
+ **New: Context-driven format hooks**
65
+
66
+ `@doswiftly/storefront-sdk/react` now ships convenience hooks that pull the active language from `useLanguageStore` (inside `<StorefrontProvider>`) and return a memoised, locale-bound formatter. Use them when you don't want to pass `locale` to every call:
67
+
68
+ ```ts
69
+ import {
70
+ useFormatPrice,
71
+ useFormatAmount,
72
+ useFormatPriceRange,
73
+ useFormatDate,
74
+ useFormatDateTime,
75
+ useFormatNumber,
76
+ useGetCurrencySymbol,
77
+ } from '@doswiftly/storefront-sdk/react';
78
+
79
+ function Cart() {
80
+ const formatPrice = useFormatPrice();
81
+ return <span>{formatPrice(item.price)}</span>;
82
+ }
83
+
84
+ function OrderRow() {
85
+ const formatDate = useFormatDate();
86
+ return <span>{formatDate(order.processedAt)}</span>;
87
+ }
88
+ ```
89
+
90
+ Each hook accepts an optional last `localeOverride` argument that wins over the store value — useful for a "show in US format" toggle on a single element:
91
+
92
+ ```ts
93
+ const formatPrice = useFormatPrice();
94
+ return <span>{formatPrice(item.price, 'en-US')}</span>;
95
+ ```
96
+
97
+ Resolution per call: `localeOverride` → `useLanguageStore().language` → runtime default.
98
+
99
+ Use the vanilla `formatPrice` / `formatDate` / etc. from `@doswiftly/storefront-sdk` (no hook) with an explicit `locale` for server components, e-mail templates, or any code path outside a provider.
100
+
101
+ `formatPercentage` is unchanged.
102
+
103
+ `@doswiftly/storefront-operations` bumped to keep linked parity — no operations changes.
104
+
105
+ ### Minor Changes
106
+
107
+ - 885c011: Re-export checkout types from the public API. `CartClient` methods added in 15.1.0 (`getAvailablePaymentMethods`, `getAvailableShippingMethods`) returned typed payloads, but the named types were not part of the public surface — component props and function signatures touching these payloads required `Awaited<ReturnType<...>>` workarounds. This change closes that gap.
108
+
109
+ **Newly exported types:**
110
+ - `PaymentMethod`, `AvailablePaymentMethods` — shape returned by `CartClient.getAvailablePaymentMethods()`
111
+ - `AvailableShippingMethod`, `AvailableShippingMethodsPayload`, `FreeShippingProgress`, `DeliveryEstimate`, `ShippingCarrier`, `DeliveryType` — shape returned by `CartClient.getAvailableShippingMethods()`
112
+ - `PickupPoint`, `PickupPointInput` — locker / pickup-point delivery (surfaced via `MailingAddress.pickupPoint` and `CartAddressInput.pickupPoint`)
113
+ - `CartAttributeInput` — input shape for `CartClient.updateAttributes()`
114
+ - `ShippingAddressInput` — input shape for the `availableShippingMethods` standalone query
115
+
116
+ **Example:**
117
+
118
+ ```ts
119
+ import type {
120
+ AvailableShippingMethodsPayload,
121
+ AvailableShippingMethod,
122
+ DeliveryType,
123
+ } from "@doswiftly/storefront-sdk";
124
+
125
+ function isPickup(method: AvailableShippingMethod): boolean {
126
+ return method.deliveryType === "PICKUP_POINT";
127
+ }
128
+ ```
129
+
130
+ No runtime changes — types already existed internally; this change only adds them to the published surface.
131
+
132
+ `@doswiftly/storefront-operations` bumped to keep linked parity — no operations changes.
133
+
134
+ - fd3d199: Expose `cart.cost.totalDiscount` and `cart.cost.totalShipping` through the `CartCost` fragment.
135
+
136
+ The schema already exposed `CartCost.totalDiscount: Money!` (aggregate of every entry in `cart.discountAllocations`) and `CartCost.totalShipping: Money` (cost of the currently selected shipping method, `null` until one is selected), but the shared `CartCost` fragment did not select them — so they were not part of the typed `Cart.cost` returned by `CartClient`. Storefronts wanting to render a single "Discounts: -X" row or a shipping summary had to either sum `discountAllocations` client-side (precision-sensitive across currencies) or query the schema directly and bypass the typed surface.
137
+
138
+ After this release, both fields are part of `Cart.cost` returned by every `CartClient` operation — `get`, `create`, `addItems`, `updateItems`, `removeItems`, `setShippingAddress`, `setBillingAddress`, `selectShippingMethod`, `selectPaymentMethod`, `updateBuyerIdentity`, `updateDiscountCodes`, `updateNote`, `updateAttributes`, `applyGiftCard`, `removeGiftCard`, `updateGiftCardRecipient`.
139
+
140
+ **Example:**
141
+
142
+ ```ts
143
+ const cart = await cartClient.get(cartId);
144
+
145
+ const summary = {
146
+ subtotal: cart.cost.subtotal,
147
+ discount: cart.cost.totalDiscount, // Money — defaults to amount 0
148
+ shipping: cart.cost.totalShipping, // Money | null — null until selectShippingMethod()
149
+ tax: cart.cost.totalTax,
150
+ total: cart.cost.total, // grand total — use directly on checkout summary
151
+ };
152
+ ```
153
+
154
+ `totalDiscount` is required (defaults to amount 0 when no discount applies). `totalShipping` is nullable — `null` means no shipping method has been selected yet, an amount of 0 means a free-shipping method was selected.
155
+
156
+ Additive — existing consumers keep every field they had before, no breaking change.
157
+
158
+ `@doswiftly/storefront-operations` bumped to keep linked parity (fragment update).
159
+
160
+ - 51091df: Expose `Cart.status` and `Cart.completedOrder` so storefronts can detect a completed (or expired / abandoned) cart on read, without having to attempt a mutation first and react to its `userErrors[].code`.
161
+
162
+ **New fields**
163
+ - `Cart.status: CartStatus!` — lifecycle status (`ACTIVE`, `ABANDONED`, `CONVERTED`, `RECOVERED`, `EXPIRED`). Only `ACTIVE` carts accept mutations; any other status rejects subsequent mutations with `CartErrorCode.ALREADY_COMPLETED`.
164
+ - `Cart.completedOrder: Order` — the order this cart converted into. Populated only when `status === CONVERTED`; null on every other status.
165
+
166
+ **Why**
167
+
168
+ When a buyer returned to the checkout page after completing their order (SSR re-render, deep link, "back" button after redirect), the SDK could only ask `cart(id)` for the cart and got the full pre-completion state back — every form value still there, the "Pay" button still active. The first mutation then failed with `ALREADY_COMPLETED`, after the buyer had already filled out fields. With `status` exposed, the storefront detects the terminal state on initial render and redirects to the order confirmation directly — using the `accessToken` on `completedOrder` for guest tracking, no extra `orderByToken` round-trip.
169
+
170
+ **Example**
171
+
172
+ ```ts
173
+ const cart = await cartClient.get(cartId);
174
+ if (!cart) {
175
+ // Cart not found — guide the buyer to create a new one
176
+ return redirect("/cart");
177
+ }
178
+ if (cart.status !== "ACTIVE") {
179
+ if (cart.completedOrder) {
180
+ // Render the order confirmation page off the data you already have
181
+ return redirect(`/order/${cart.completedOrder.accessToken}`);
182
+ }
183
+ // Abandoned / expired — start a fresh cart
184
+ return redirect("/cart/new");
185
+ }
186
+ // Render the checkout form normally
187
+ ```
188
+
189
+ Additive — every existing query that selects the shared `Cart` fragment now sees the two new fields automatically. No breaking change.
190
+
191
+ `@doswiftly/storefront-operations` bumped to keep linked parity — schema sync delivers the new fields and the `CartStatus` enum.
192
+
193
+ - f4efab9: Add `ShopConfigFields` fragment + `query ShopConfig` for `<StorefrontProvider>` setup.
194
+
195
+ `<StorefrontProvider shopData={...}>` from `@doswiftly/storefront-sdk/react` expects a `ShopConfig` payload with a specific shape: currency setup (including `localeToCurrencyMap`), language setup, and bot protection. Until now the storefront had to hand-write the field selection, and it was easy to miss `localeToCurrencyMap` (used internally by the SDK for browser-locale-based currency detection) or to add an extra field that didn't match the `ShopConfig` interface.
196
+
197
+ **New**
198
+ - `fragment ShopConfigFields on Shop` — minimal selection that matches the `ShopConfig` interface 1:1.
199
+ - `query ShopConfig { shop { ...ShopConfigFields } }` — ready-to-use query for the provider.
200
+
201
+ **Example**
202
+
203
+ ```ts
204
+ const SHOP_CONFIG_QUERY = /* GraphQL */ `
205
+ query ShopConfig {
206
+ shop {
207
+ ...ShopConfigFields
208
+ }
209
+ }
210
+ `;
211
+
212
+ // In a Server Component:
213
+ const { data } = await execute(SHOP_CONFIG_QUERY);
214
+ return <StorefrontProvider shopData={data.shop}>{children}</StorefrontProvider>;
215
+ ```
216
+
217
+ Use the larger `Shop` fragment + `query Shop` when you also need branding / contact / business hours for your UI; use `ShopConfig` when you only need to bootstrap the provider.
218
+
219
+ Additive — no breaking change.
220
+
221
+ `@doswiftly/storefront-sdk` bumped to keep linked parity — no code change.
222
+
223
+ ### Patch Changes
224
+
225
+ - 7ce8ac4: Clarify how `apiUrl` and `shopSlug` are configured.
226
+
227
+ `createStorefrontClient` and `<StorefrontProvider>` take `apiUrl` and `shopSlug` as **explicit `config`** — the SDK does not read environment variables, sniff hostnames, or inspect request headers. The storefront supplies the values; the SDK uses them verbatim.
228
+
229
+ Scaffolded storefronts ship a config helper (`lib/graphql/config.ts`) that resolves the two values from these sources, in order:
230
+ 1. `doswiftly.config.ts` (preferred — committed file generated at `doswiftly init`)
231
+ 2. `NEXT_PUBLIC_API_URL` + `NEXT_PUBLIC_SHOP_SLUG` (fallback, used for local development)
232
+ 3. `http://localhost:8000` + `demo-shop` (defaults — smoke test only)
233
+
234
+ Scratch-built storefronts can skip the helper and pass values into `config={}` directly.
235
+
236
+ **What's new**
237
+ - `@doswiftly/storefront-sdk` README: new `## Configuration` section between `## Installation` and `## Quick Start`. Documents the explicit-config requirement, the three-source resolver shipped with scaffolded storefronts, and when env-var wiring actually matters.
238
+ - `@doswiftly/storefront-operations` `AGENTS.md`: new `### Configuration sources` convention inside `## Critical conventions — DO NOT hallucinate`. AI assistants now prefer `doswiftly.config.ts` and only fall back to the canonical env-var names.
239
+
240
+ Documentation only — no code change.
241
+
3
242
  ## 15.1.0
4
243
 
5
244
  ### Minor Changes
package/README.md CHANGED
@@ -163,6 +163,7 @@ full executable body of each operation.
163
163
  | Operation | Description |
164
164
  | --- | --- |
165
165
  | `Shop` | Returns shop configuration: name, base + supported currencies, supported locales, branding (logo, colors, fonts, social links), contact info, active payment methods, brand metadata, money format template, and the list of countries the shop ships to. Public; no auth required. Call once per session and cache — almost everything else is contextualized by the shop returned here. |
166
+ | `ShopConfig` | Minimal Shop payload for `<StorefrontProvider shopData={...}>` from `@doswiftly/storefront-sdk/react`. Returns exactly the fields the SDK's `ShopConfig` interface declares — currency setup (with `localeToCurrencyMap` for browser-locale-based currency detection), language setup, and bot protection. Cache for the session; refetch when the merchant updates currency / language settings or you want to pick up new bot-protection rules. |
166
167
 
167
168
  #### Products
168
169
 
@@ -476,7 +477,7 @@ full executable body of each operation.
476
477
 
477
478
  | Fragment | On Type | Description |
478
479
  | --- | --- | --- |
479
- | `CartCost` | `CartCost` | Cart-level totals — subtotal, total, tax, duty, checkout charge. Spread inside the `Cart` fragment. |
480
+ | `CartCost` | `CartCost` | Cart-level totals — subtotal, total, tax, duty, discount, shipping, checkout charge. Spread inside the `Cart` fragment. |
480
481
  | `CartLineCost` | `CartLineCost` | Per-line cost breakdown — unit price, line subtotal/total, compare-at unit price (for showing strikethroughs). Spread inside `CartLine`. |
481
482
  | `AttributeSelection` | `AttributeSelection` | Typed snapshot of a customer-filled product attribute (configurator selection) on a cart line. Includes the chosen option / text value, fillingMode, billingMode, surcharge, tax class, and any linked variant. Distinct from `CartLine.attributes` which holds raw key-value line item properties (free-form, untyped). |
482
483
  | `CartLine` | `CartLine` | A single line in the cart — variant + quantity + per-line cost, plus dual attribute storage: `attributes[]` for free-form key-value properties (gift wrap, engraving notes), `attributeSelections[]` for typed configurator answers. |
@@ -501,6 +502,7 @@ full executable body of each operation.
501
502
  | `BotProtectionProvider` | `BotProtectionProviderInfo` | Bot-protection provider config (provider name, site key, script URL) for storefront-side challenge widgets. |
502
503
  | `BotProtection` | `BotProtectionInfo` | Bot-protection setup for the shop — primary + fallback provider, and the list of `protectedOperations` (mutation names that require a challenge token). Spread inside `Shop`. |
503
504
  | `Shop` | `Shop` | The shop itself — name, primary domain, currencies + locales, branding, contact info, business hours, bot-protection config. Spread on the root `shop` query; cache for the session. |
505
+ | `ShopConfigFields` | `Shop` | Minimal Shop fields consumed by `<StorefrontProvider shopData={...}>` from `@doswiftly/storefront-sdk/react`. Spread this in your `shop` query to get a response shape that matches the SDK `ShopConfig` interface 1:1 — no manual field selection, no drift when the SDK adds optional fields. Use the larger `Shop` fragment when you also need branding / contact / business hours for your UI. |
504
506
 
505
507
  #### Payment Methods
506
508
 
package/fragments.graphql CHANGED
@@ -347,7 +347,7 @@ fragment Order on Order {
347
347
  # Cart
348
348
  # ============================================
349
349
 
350
- # Cart-level totals — subtotal, total, tax, duty, checkout charge. Spread inside the `Cart` fragment.
350
+ # Cart-level totals — subtotal, total, tax, duty, discount, shipping, checkout charge. Spread inside the `Cart` fragment.
351
351
  fragment CartCost on CartCost {
352
352
  total {
353
353
  ...Money
@@ -364,6 +364,12 @@ fragment CartCost on CartCost {
364
364
  checkoutCharge {
365
365
  ...Money
366
366
  }
367
+ totalDiscount {
368
+ ...Money
369
+ }
370
+ totalShipping {
371
+ ...Money
372
+ }
367
373
  }
368
374
 
369
375
  # Per-line cost breakdown — unit price, line subtotal/total, compare-at unit price (for showing strikethroughs). Spread inside `CartLine`.
@@ -505,6 +511,15 @@ fragment Cart on Cart {
505
511
  requiresShipping
506
512
  createdAt
507
513
  updatedAt
514
+ status
515
+ completedOrder {
516
+ id
517
+ orderNumber
518
+ accessToken
519
+ status
520
+ paymentStatus
521
+ fulfillmentStatus
522
+ }
508
523
  }
509
524
 
510
525
  # Shipping method selected on a cart (D8 term unification — wcześniej ShippingRate). Returned przez `cart.selectedShippingMethod` po `cartSelectShippingMethod` mutation.
@@ -661,6 +676,21 @@ fragment Shop on Shop {
661
676
  }
662
677
  }
663
678
 
679
+ # Minimal Shop fields consumed by `<StorefrontProvider shopData={...}>` from `@doswiftly/storefront-sdk/react`. Spread this in your `shop` query to get a response shape that matches the SDK `ShopConfig` interface 1:1 — no manual field selection, no drift when the SDK adds optional fields. Use the larger `Shop` fragment when you also need branding / contact / business hours for your UI.
680
+ fragment ShopConfigFields on Shop {
681
+ currencyCode
682
+ supportedCurrencies
683
+ localeToCurrencyMap {
684
+ locale
685
+ currency
686
+ }
687
+ defaultLanguage
688
+ supportedLanguages
689
+ botProtection {
690
+ ...BotProtection
691
+ }
692
+ }
693
+
664
694
  # ============================================
665
695
  # Payment Methods
666
696
  # ============================================
package/llms-full.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # DoSwiftly Storefront Operations — Full Reference
2
2
 
3
- > Schema version: **15.1.0**
4
- > 51 queries · 40 mutations · 101 fragments
3
+ > Schema version: **16.0.0**
4
+ > 52 queries · 40 mutations · 102 fragments
5
5
 
6
6
  Auto-generated from `.graphql` source files. Do not edit by hand — this file is
7
7
  regenerated on every release to match the published schema.
@@ -37,6 +37,25 @@ query Shop {
37
37
  }
38
38
  ```
39
39
 
40
+ ### Query: `ShopConfig`
41
+
42
+ **Section**: Shop
43
+
44
+ **Description**: Minimal Shop payload for `<StorefrontProvider shopData={...}>` from `@doswiftly/storefront-sdk/react`. Returns exactly the fields the SDK's `ShopConfig` interface declares — currency setup (with `localeToCurrencyMap` for browser-locale-based currency detection), language setup, and bot protection. Cache for the session; refetch when the merchant updates currency / language settings or you want to pick up new bot-protection rules.
45
+
46
+ **Variables**: none
47
+
48
+ **Fragments used**: `ShopConfigFields`
49
+
50
+ **GraphQL**:
51
+ ```graphql
52
+ query ShopConfig {
53
+ shop {
54
+ ...ShopConfigFields
55
+ }
56
+ }
57
+ ```
58
+
40
59
  ### Query: `Product`
41
60
 
42
61
  **Section**: Products
@@ -2920,7 +2939,7 @@ fragment Order on Order {
2920
2939
 
2921
2940
  **Section**: Cart
2922
2941
 
2923
- **Description**: Cart-level totals — subtotal, total, tax, duty, checkout charge. Spread inside the `Cart` fragment.
2942
+ **Description**: Cart-level totals — subtotal, total, tax, duty, discount, shipping, checkout charge. Spread inside the `Cart` fragment.
2924
2943
 
2925
2944
  **Uses fragments**: `Money`
2926
2945
 
@@ -2942,6 +2961,12 @@ fragment CartCost on CartCost {
2942
2961
  checkoutCharge {
2943
2962
  ...Money
2944
2963
  }
2964
+ totalDiscount {
2965
+ ...Money
2966
+ }
2967
+ totalShipping {
2968
+ ...Money
2969
+ }
2945
2970
  }
2946
2971
  ```
2947
2972
 
@@ -3147,6 +3172,15 @@ fragment Cart on Cart {
3147
3172
  requiresShipping
3148
3173
  createdAt
3149
3174
  updatedAt
3175
+ status
3176
+ completedOrder {
3177
+ id
3178
+ orderNumber
3179
+ accessToken
3180
+ status
3181
+ paymentStatus
3182
+ fulfillmentStatus
3183
+ }
3150
3184
  }
3151
3185
  ```
3152
3186
 
@@ -3406,6 +3440,31 @@ fragment Shop on Shop {
3406
3440
  }
3407
3441
  ```
3408
3442
 
3443
+ ### Fragment: `ShopConfigFields` on `Shop`
3444
+
3445
+ **Section**: Shop
3446
+
3447
+ **Description**: Minimal Shop fields consumed by `<StorefrontProvider shopData={...}>` from `@doswiftly/storefront-sdk/react`. Spread this in your `shop` query to get a response shape that matches the SDK `ShopConfig` interface 1:1 — no manual field selection, no drift when the SDK adds optional fields. Use the larger `Shop` fragment when you also need branding / contact / business hours for your UI.
3448
+
3449
+ **Uses fragments**: `BotProtection`
3450
+
3451
+ **GraphQL**:
3452
+ ```graphql
3453
+ fragment ShopConfigFields on Shop {
3454
+ currencyCode
3455
+ supportedCurrencies
3456
+ localeToCurrencyMap {
3457
+ locale
3458
+ currency
3459
+ }
3460
+ defaultLanguage
3461
+ supportedLanguages
3462
+ botProtection {
3463
+ ...BotProtection
3464
+ }
3465
+ }
3466
+ ```
3467
+
3409
3468
  ### Fragment: `PaymentMethod` on `PaymentMethod`
3410
3469
 
3411
3470
  **Section**: Payment Methods
package/operations.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "schemaVersion": "15.1.0",
2
+ "schemaVersion": "16.0.0",
3
3
  "queries": [
4
4
  {
5
5
  "name": "Shop",
@@ -12,6 +12,17 @@
12
12
  ],
13
13
  "body": "query Shop {\n shop {\n ...Shop\n }\n}"
14
14
  },
15
+ {
16
+ "name": "ShopConfig",
17
+ "kind": "query",
18
+ "section": "Shop",
19
+ "description": "Minimal Shop payload for `<StorefrontProvider shopData={...}>` from `@doswiftly/storefront-sdk/react`. Returns exactly the fields the SDK's `ShopConfig` interface declares — currency setup (with `localeToCurrencyMap` for browser-locale-based currency detection), language setup, and bot protection. Cache for the session; refetch when the merchant updates currency / language settings or you want to pick up new bot-protection rules.",
20
+ "variables": [],
21
+ "fragmentRefs": [
22
+ "ShopConfigFields"
23
+ ],
24
+ "body": "query ShopConfig {\n shop {\n ...ShopConfigFields\n }\n}"
25
+ },
15
26
  {
16
27
  "name": "Product",
17
28
  "kind": "query",
@@ -2072,12 +2083,12 @@
2072
2083
  "name": "CartCost",
2073
2084
  "kind": "fragment",
2074
2085
  "section": "Cart",
2075
- "description": "Cart-level totals — subtotal, total, tax, duty, checkout charge. Spread inside the `Cart` fragment.",
2086
+ "description": "Cart-level totals — subtotal, total, tax, duty, discount, shipping, checkout charge. Spread inside the `Cart` fragment.",
2076
2087
  "variables": [],
2077
2088
  "fragmentRefs": [
2078
2089
  "Money"
2079
2090
  ],
2080
- "body": "fragment CartCost on CartCost {\n total {\n ...Money\n }\n subtotal {\n ...Money\n }\n totalTax {\n ...Money\n }\n totalDuty {\n ...Money\n }\n checkoutCharge {\n ...Money\n }\n}",
2091
+ "body": "fragment CartCost on CartCost {\n total {\n ...Money\n }\n subtotal {\n ...Money\n }\n totalTax {\n ...Money\n }\n totalDuty {\n ...Money\n }\n checkoutCharge {\n ...Money\n }\n totalDiscount {\n ...Money\n }\n totalShipping {\n ...Money\n }\n}",
2081
2092
  "onType": "CartCost"
2082
2093
  },
2083
2094
  {
@@ -2166,7 +2177,7 @@
2166
2177
  "MailingAddress",
2167
2178
  "PageInfo"
2168
2179
  ],
2169
- "body": "fragment Cart on Cart {\n id\n checkoutUrl\n totalQuantity\n cost {\n ...CartCost\n }\n lines(first: 100) {\n edges {\n cursor\n node {\n ... on CartLine {\n ...CartLine\n }\n }\n }\n nodes {\n ... on CartLine {\n ...CartLine\n }\n }\n pageInfo {\n ...PageInfo\n }\n totalCount\n }\n buyerIdentity {\n ...CartBuyerIdentity\n }\n discountCodes {\n ...CartDiscountCode\n }\n discountAllocations {\n ...CartDiscountAllocation\n }\n note\n attributes {\n key\n value\n }\n email\n phone\n shippingAddress {\n ...MailingAddress\n }\n billingAddress {\n ...MailingAddress\n }\n selectedShippingMethod {\n ...CartShippingMethod\n }\n selectedPaymentMethod {\n ...CartSelectedPaymentMethod\n }\n appliedGiftCards {\n ...CartAppliedGiftCard\n }\n requiresShipping\n createdAt\n updatedAt\n}",
2180
+ "body": "fragment Cart on Cart {\n id\n checkoutUrl\n totalQuantity\n cost {\n ...CartCost\n }\n lines(first: 100) {\n edges {\n cursor\n node {\n ... on CartLine {\n ...CartLine\n }\n }\n }\n nodes {\n ... on CartLine {\n ...CartLine\n }\n }\n pageInfo {\n ...PageInfo\n }\n totalCount\n }\n buyerIdentity {\n ...CartBuyerIdentity\n }\n discountCodes {\n ...CartDiscountCode\n }\n discountAllocations {\n ...CartDiscountAllocation\n }\n note\n attributes {\n key\n value\n }\n email\n phone\n shippingAddress {\n ...MailingAddress\n }\n billingAddress {\n ...MailingAddress\n }\n selectedShippingMethod {\n ...CartShippingMethod\n }\n selectedPaymentMethod {\n ...CartSelectedPaymentMethod\n }\n appliedGiftCards {\n ...CartAppliedGiftCard\n }\n requiresShipping\n createdAt\n updatedAt\n status\n completedOrder {\n id\n orderNumber\n accessToken\n status\n paymentStatus\n fulfillmentStatus\n }\n}",
2170
2181
  "onType": "Cart"
2171
2182
  },
2172
2183
  {
@@ -2306,6 +2317,18 @@
2306
2317
  "body": "fragment Shop on Shop {\n id\n name\n description\n primaryDomain {\n host\n url\n isSslEnabled\n }\n currencyCode\n supportedCurrencies\n paymentCurrencies\n defaultLanguage\n supportedLanguages\n logo {\n ...Image\n }\n contactEmail\n contactPhone\n address {\n ...ShopAddress\n }\n businessHours {\n ...BusinessHour\n }\n branding {\n ...ShopBranding\n }\n botProtection {\n ...BotProtection\n }\n}",
2307
2318
  "onType": "Shop"
2308
2319
  },
2320
+ {
2321
+ "name": "ShopConfigFields",
2322
+ "kind": "fragment",
2323
+ "section": "Shop",
2324
+ "description": "Minimal Shop fields consumed by `<StorefrontProvider shopData={...}>` from `@doswiftly/storefront-sdk/react`. Spread this in your `shop` query to get a response shape that matches the SDK `ShopConfig` interface 1:1 — no manual field selection, no drift when the SDK adds optional fields. Use the larger `Shop` fragment when you also need branding / contact / business hours for your UI.",
2325
+ "variables": [],
2326
+ "fragmentRefs": [
2327
+ "BotProtection"
2328
+ ],
2329
+ "body": "fragment ShopConfigFields on Shop {\n currencyCode\n supportedCurrencies\n localeToCurrencyMap {\n locale\n currency\n }\n defaultLanguage\n supportedLanguages\n botProtection {\n ...BotProtection\n }\n}",
2330
+ "onType": "Shop"
2331
+ },
2309
2332
  {
2310
2333
  "name": "PaymentMethod",
2311
2334
  "kind": "fragment",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doswiftly/storefront-operations",
3
- "version": "15.1.0",
3
+ "version": "16.0.0",
4
4
  "description": "GraphQL operations for DoSwiftly Storefront - SSOT from backend",
5
5
  "homepage": "https://doswiftly.pl",
6
6
  "publishConfig": {
package/queries.graphql CHANGED
@@ -14,6 +14,13 @@ query Shop {
14
14
  }
15
15
  }
16
16
 
17
+ # Minimal Shop payload for `<StorefrontProvider shopData={...}>` from `@doswiftly/storefront-sdk/react`. Returns exactly the fields the SDK's `ShopConfig` interface declares — currency setup (with `localeToCurrencyMap` for browser-locale-based currency detection), language setup, and bot protection. Cache for the session; refetch when the merchant updates currency / language settings or you want to pick up new bot-protection rules.
18
+ query ShopConfig {
19
+ shop {
20
+ ...ShopConfigFields
21
+ }
22
+ }
23
+
17
24
  # ============================================
18
25
  # Products
19
26
  # ============================================
package/schema.graphql CHANGED
@@ -903,6 +903,11 @@ type Cart implements Node {
903
903
  """
904
904
  checkoutUrl: URL
905
905
 
906
+ """
907
+ The order that this cart converted into. Populated only when `status` is `CONVERTED` — null on every other status. Use this to render the order confirmation page (subtotals, accessToken for guest tracking) directly off the cart you already loaded, without a second `orderByToken` round-trip.
908
+ """
909
+ completedOrder: Order
910
+
906
911
  """
907
912
  Cost breakdown for the cart (subtotal, tax, shipping, discount, grand total).
908
913
  """
@@ -965,6 +970,11 @@ type Cart implements Node {
965
970
  """
966
971
  shippingAddress: MailingAddress
967
972
 
973
+ """
974
+ Lifecycle status — `ACTIVE` for the editable working cart, terminal otherwise. Check this on SSR before rendering the checkout form: a non-`ACTIVE` cart should redirect (typically to the order confirmation when `completedOrder` is populated) instead of presenting a form whose first mutation fails with `CartErrorCode.ALREADY_COMPLETED`.
975
+ """
976
+ status: CartStatus!
977
+
968
978
  """
969
979
  Sum of `quantity` across all lines — the badge number for the cart icon.
970
980
  """
@@ -1664,6 +1674,17 @@ type CartShippingMethod {
1664
1674
  title: String!
1665
1675
  }
1666
1676
 
1677
+ """
1678
+ Cart lifecycle status. `ACTIVE` is the only editable state — every other value is terminal and rejects mutations with `CartErrorCode.ALREADY_COMPLETED`. `CONVERTED` carries an associated `completedOrder`; query that to redirect the buyer to their order confirmation. `EXPIRED` / `ABANDONED` are cleanup states with no order; create a fresh cart. `RECOVERED` is a previously-abandoned cart that the buyer returned to via a recovery link.
1679
+ """
1680
+ enum CartStatus {
1681
+ ABANDONED
1682
+ ACTIVE
1683
+ CONVERTED
1684
+ EXPIRED
1685
+ RECOVERED
1686
+ }
1687
+
1667
1688
  """Result of `cartUpdateAttributes`."""
1668
1689
  type CartUpdateAttributesPayload {
1669
1690
  """The updated cart on success."""