@doswiftly/storefront-sdk 22.8.1 → 22.10.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 +64 -0
- package/README.md +23 -13
- package/dist/core/generated/operation-types.d.ts +5 -3
- package/dist/core/generated/operation-types.d.ts.map +1 -1
- package/dist/core/image.d.ts +9 -0
- package/dist/core/image.d.ts.map +1 -1
- package/dist/core/image.js +24 -14
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/middleware/cart-secret.d.ts +36 -8
- package/dist/core/middleware/cart-secret.d.ts.map +1 -1
- package/dist/core/middleware/cart-secret.js +46 -12
- package/dist/core/operations/cart.d.ts.map +1 -1
- package/dist/core/operations/cart.js +1 -0
- package/dist/next/image-loader.d.ts +4 -4
- package/dist/next/image-loader.d.ts.map +1 -1
- package/dist/next/image-loader.js +5 -4
- package/dist/react/providers/storefront-client-provider.d.ts.map +1 -1
- package/dist/react/providers/storefront-client-provider.js +4 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 22.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3961ca4: Attach the cart access secret (`x-cart-secret`) only to cart operations.
|
|
8
|
+
|
|
9
|
+
Previously the header was sent on every request whenever a cart cookie was
|
|
10
|
+
present, which kept public catalog reads (products, search, recommendations,
|
|
11
|
+
payment methods) off the shared response cache for any visitor who had a cart.
|
|
12
|
+
The secret is now attached only to cart-scoped operations, so those public
|
|
13
|
+
reads become cacheable again — faster responses, with no change needed in your
|
|
14
|
+
code.
|
|
15
|
+
|
|
16
|
+
A new `isCartScopedOperation` helper is exported, and `cartSecretMiddleware` /
|
|
17
|
+
`serverCartSecretMiddleware` accept an optional classifier as their second
|
|
18
|
+
argument. Cart operations are recognised by a `Cart` operation-name prefix. If
|
|
19
|
+
you issue custom cart queries through the SDK, name them with a `Cart` prefix,
|
|
20
|
+
or pass your own classifier:
|
|
21
|
+
`cartSecretMiddleware(getSecret, (operationName) => ...)`.
|
|
22
|
+
|
|
23
|
+
## 22.9.0
|
|
24
|
+
|
|
25
|
+
### Minor Changes
|
|
26
|
+
|
|
27
|
+
- 36c28ff: Add `customerNote` to the storefront `Order` type — the note a buyer left at checkout (delivery instructions, gift message, etc.). It is selected by the order fragment, so it is available on every order query, and is `null` when the buyer left no note.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
// Order confirmation / detail page
|
|
31
|
+
const note = order.customerNote;
|
|
32
|
+
if (note) {
|
|
33
|
+
return <p className="order-note">{note}</p>;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- a399d36: Image loader now produces clean image URLs (no internal storage prefix) on custom domains.
|
|
38
|
+
|
|
39
|
+
**Why**: when your storefront is served from its own image domain, the built `<Image>` URLs no longer carry the `/s/{shopId}` storage prefix (`https://img.yourshop.com/_next/static/media/hero.webp?width=256` instead of `.../s/{shopId}/_next/...`). The stored object is unchanged — the platform restores the prefix toward storage — so this is purely a nicer public URL. Branding-friendly, and one fewer internal detail in the markup.
|
|
40
|
+
|
|
41
|
+
**This is automatic — no code change.** The common `createImageLoader()` setup reads the flag from the platform-injected env var, so clean URLs simply turn on when your storefront is served from a custom domain configured for them:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// lib/image-loader.ts — unchanged; clean URLs turn on automatically.
|
|
45
|
+
import { createImageLoader } from "@doswiftly/storefront-sdk/next";
|
|
46
|
+
export default createImageLoader();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Additive (backward-compatible)**:
|
|
50
|
+
1. `ImageLoaderConfig` gains an optional `cleanUrl?: boolean` (defaults to `false`).
|
|
51
|
+
2. `createImageLoader()` reads it automatically from `NEXT_PUBLIC_ASSET_CLEAN_URL`.
|
|
52
|
+
|
|
53
|
+
When `cleanUrl` is off (the default), URLs keep the `/s/{shopId}` prefix exactly as before — existing storefronts are unaffected. The boolean is also accepted by the low-level `buildImageLoaderUrl` for non-`<Image>` usage (e.g. CSS backgrounds) or tests:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { buildImageLoaderUrl } from "@doswiftly/storefront-sdk";
|
|
57
|
+
buildImageLoaderUrl(
|
|
58
|
+
{ shopId, version, cleanUrl: true, cdnBase: "https://img.yourshop.com" },
|
|
59
|
+
{ src: "/hero.webp", width: 256 },
|
|
60
|
+
);
|
|
61
|
+
// → https://img.yourshop.com/public/hero.webp?width=256&v=<version>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Migration checklist for existing storefronts**:
|
|
65
|
+
- [ ] None required — the default behavior is unchanged. Re-deploy to pick up clean URLs once your custom domain is configured.
|
|
66
|
+
|
|
3
67
|
## 22.8.1
|
|
4
68
|
|
|
5
69
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -197,7 +197,7 @@ const client = createStorefrontClient({
|
|
|
197
197
|
apiUrl: 'https://api.doswiftly.pl',
|
|
198
198
|
shopSlug: 'my-shop',
|
|
199
199
|
middleware: [
|
|
200
|
-
cartSecretMiddleware(() => cartSecret), // lazy getter
|
|
200
|
+
cartSecretMiddleware(() => cartSecret), // lazy getter; secret rides cart ops only
|
|
201
201
|
retryMiddleware({ maxRetries: 2 }),
|
|
202
202
|
timeoutMiddleware({ timeout: 5000 }),
|
|
203
203
|
errorMiddleware(), // ALWAYS LAST
|
|
@@ -425,17 +425,24 @@ Cart access is authorized by **possession of a secret**, not by the customer
|
|
|
425
425
|
session. The `cart-id` cookie stores a composite value `"<cartId>.<secret>"`
|
|
426
426
|
(30 days, SSR/edge-visible, not httpOnly — the cart carries no payment data).
|
|
427
427
|
`CartClient.create()` and `recoveryRedeem()` reveal the secret **once**; the SDK
|
|
428
|
-
persists it into the cookie for you.
|
|
429
|
-
the `x-cart-secret` header via middleware
|
|
428
|
+
persists it into the cookie for you. **Cart operations** then carry the secret in
|
|
429
|
+
the `x-cart-secret` header via middleware — public reads (product listings,
|
|
430
|
+
search, recommendations, payment methods) do **not**, so they stay eligible for
|
|
431
|
+
the shared cache even when the visitor has a cart:
|
|
430
432
|
|
|
431
433
|
- **Browser**: `StorefrontProvider` wires `cartSecretMiddleware` automatically —
|
|
432
|
-
the secret is read lazily from the cookie
|
|
433
|
-
secret is picked up without rebuilding the client.
|
|
434
|
+
the secret is read lazily from the cookie and attached only to cart
|
|
435
|
+
operations, so a rotated secret is picked up without rebuilding the client.
|
|
434
436
|
- **Server (SSR/edge)**: prepend `serverCartSecretMiddleware(await readCartCredentials())`
|
|
435
437
|
to your server client — see [Server-side](#server-side-reactserver).
|
|
436
438
|
- **Custom runtimes**: `cartSecretMiddleware(() => secret)` + the
|
|
437
439
|
`parseCartCookieValue` / `formatCartCookieValue` helpers.
|
|
438
440
|
|
|
441
|
+
Cart operations are recognised by a `Cart` operation-name prefix
|
|
442
|
+
(`isCartScopedOperation`). If you issue custom cart queries, name them with a
|
|
443
|
+
`Cart` prefix, or pass your own classifier as the second argument:
|
|
444
|
+
`cartSecretMiddleware(getSecret, (operationName) => ...)`.
|
|
445
|
+
|
|
439
446
|
A cookie without the secret half (or a stale capability) makes the cart
|
|
440
447
|
unreachable — mutations reject with `CART_NOT_FOUND` and the standard recovery
|
|
441
448
|
flow recreates a fresh cart.
|
|
@@ -777,13 +784,16 @@ The loader is global and handles each `<Image src>` by category:
|
|
|
777
784
|
| Image imported in code (`import hero from './hero.webp'`) | Routed through the CDN — the framework bundles it under `/_next/static/media/`, the loader resizes it per `srcset` (no code change needed) |
|
|
778
785
|
| Other build asset (`/_next/*` JS/CSS, `/_nuxt/`, `/_astro/`, `/_app/`), absolute external URL, `data:` URI, SVG | Returned unchanged |
|
|
779
786
|
|
|
780
|
-
The loader reads `NEXT_PUBLIC_SHOP_ID`, `NEXT_PUBLIC_DEPLOYMENT_COMMIT`,
|
|
781
|
-
`NEXT_PUBLIC_IMGPROXY_BASE` — all injected by the platform
|
|
782
|
-
they are absent (e.g. local `doswiftly dev`, where `public/` images
|
|
783
|
-
dev server) the loader leaves local `public/` images untouched; product
|
|
784
|
-
absolute CDN URLs, are unaffected.
|
|
785
|
-
|
|
786
|
-
|
|
787
|
+
The loader reads `NEXT_PUBLIC_SHOP_ID`, `NEXT_PUBLIC_DEPLOYMENT_COMMIT`,
|
|
788
|
+
`NEXT_PUBLIC_IMGPROXY_BASE`, and `NEXT_PUBLIC_ASSET_CLEAN_URL` — all injected by the platform
|
|
789
|
+
at build/deploy time. When they are absent (e.g. local `doswiftly dev`, where `public/` images
|
|
790
|
+
are served by the dev server) the loader leaves local `public/` images untouched; product
|
|
791
|
+
images, being absolute CDN URLs, are unaffected. `NEXT_PUBLIC_ASSET_CLEAN_URL` is set to `true`
|
|
792
|
+
only when your storefront is served from a custom domain configured for clean URLs — then the
|
|
793
|
+
built URLs omit the internal storage prefix (`{host}/_next/static/media/...` instead of
|
|
794
|
+
`{host}/s/{shopId}/_next/...`); the stored object is unchanged. `<Image quality>` is a no-op —
|
|
795
|
+
quality is applied server-side (the format is still auto-negotiated AVIF/WebP). Pass explicit
|
|
796
|
+
overrides only for non-standard setups: `createImageLoader({ shopId, version, cdnBase, cleanUrl })`.
|
|
787
797
|
|
|
788
798
|
For non-`<Image>` usage (CSS backgrounds, raw `<img>`), build CDN URLs yourself
|
|
789
799
|
with the pure helper `buildImageLoaderUrl` from `@doswiftly/storefront-sdk`.
|
|
@@ -803,7 +813,7 @@ replay; a 401 on a mutation fires the `session-expired` signal instead.
|
|
|
803
813
|
```typescript
|
|
804
814
|
import {
|
|
805
815
|
authMiddleware, // Authorization: Bearer <token> (lazy getter)
|
|
806
|
-
cartSecretMiddleware, // x-cart-secret header (
|
|
816
|
+
cartSecretMiddleware, // x-cart-secret header (cart operations only)
|
|
807
817
|
currencyMiddleware, // X-Preferred-Currency header
|
|
808
818
|
languageMiddleware, // X-Language header (skipped when null — intentional)
|
|
809
819
|
botProtectionMiddleware, // challenge token for protected mutations only
|
|
@@ -2439,6 +2439,8 @@ export type Order = Node & {
|
|
|
2439
2439
|
cancelledAt?: Maybe<Scalars['DateTime']['output']>;
|
|
2440
2440
|
/** When the order was confirmed (e.g. payment authorised / approved). Null until confirmation. */
|
|
2441
2441
|
confirmedAt?: Maybe<Scalars['DateTime']['output']>;
|
|
2442
|
+
/** The note the buyer left at checkout (delivery instructions, gift message, etc.). Null when no note was provided. */
|
|
2443
|
+
customerNote?: Maybe<Scalars['String']['output']>;
|
|
2442
2444
|
/** Per-code discount allocations on the order (parity with `Cart.discountAllocations`). One entry per code that reduced the price; empty when no discount applied. The sum of `amount` equals the order-level discount. */
|
|
2443
2445
|
discountAllocations: Array<OrderDiscountAllocation>;
|
|
2444
2446
|
/** When the order expired (e.g. pending payment timed out). Null when not expired. */
|
|
@@ -4182,7 +4184,7 @@ export type CartCompleteMutationVariables = Exact<{
|
|
|
4182
4184
|
}>;
|
|
4183
4185
|
export type CartCompleteMutation = {
|
|
4184
4186
|
cartComplete: {
|
|
4185
|
-
order?: Maybe<(Pick<Order, 'id' | 'orderNumber' | 'accessToken' | 'status' | 'paymentStatus' | 'fulfillmentStatus' | 'processedAt' | 'confirmedAt' | 'cancelledAt' | 'expiredAt' | 'itemCount' | 'canCreatePayment' | 'paymentMethodType'> & {
|
|
4187
|
+
order?: Maybe<(Pick<Order, 'id' | 'orderNumber' | 'accessToken' | 'status' | 'paymentStatus' | 'fulfillmentStatus' | 'processedAt' | 'confirmedAt' | 'cancelledAt' | 'expiredAt' | 'itemCount' | 'customerNote' | 'canCreatePayment' | 'paymentMethodType'> & {
|
|
4186
4188
|
totals: {
|
|
4187
4189
|
total: Pick<Money, 'amount' | 'currencyCode'>;
|
|
4188
4190
|
subtotal: Pick<Money, 'amount' | 'currencyCode'>;
|
|
@@ -5307,7 +5309,7 @@ export type OrderByTokenQueryVariables = Exact<{
|
|
|
5307
5309
|
email?: InputMaybe<Scalars['String']['input']>;
|
|
5308
5310
|
}>;
|
|
5309
5311
|
export type OrderByTokenQuery = {
|
|
5310
|
-
orderByToken?: Maybe<(Pick<Order, 'id' | 'orderNumber' | 'accessToken' | 'status' | 'paymentStatus' | 'fulfillmentStatus' | 'processedAt' | 'confirmedAt' | 'cancelledAt' | 'expiredAt' | 'itemCount' | 'canCreatePayment' | 'paymentMethodType'> & {
|
|
5312
|
+
orderByToken?: Maybe<(Pick<Order, 'id' | 'orderNumber' | 'accessToken' | 'status' | 'paymentStatus' | 'fulfillmentStatus' | 'processedAt' | 'confirmedAt' | 'cancelledAt' | 'expiredAt' | 'itemCount' | 'customerNote' | 'canCreatePayment' | 'paymentMethodType'> & {
|
|
5311
5313
|
totals: {
|
|
5312
5314
|
total: Pick<Money, 'amount' | 'currencyCode'>;
|
|
5313
5315
|
subtotal: Pick<Money, 'amount' | 'currencyCode'>;
|
|
@@ -5568,7 +5570,7 @@ export type FreeShippingProgressFragment = (Pick<FreeShippingProgress, 'qualifie
|
|
|
5568
5570
|
threshold?: Maybe<Pick<Money, 'amount' | 'currencyCode'>>;
|
|
5569
5571
|
remaining?: Maybe<Pick<Money, 'amount' | 'currencyCode'>>;
|
|
5570
5572
|
});
|
|
5571
|
-
export type OrderFragment = (Pick<Order, 'id' | 'orderNumber' | 'accessToken' | 'status' | 'paymentStatus' | 'fulfillmentStatus' | 'processedAt' | 'confirmedAt' | 'cancelledAt' | 'expiredAt' | 'itemCount' | 'canCreatePayment' | 'paymentMethodType'> & {
|
|
5573
|
+
export type OrderFragment = (Pick<Order, 'id' | 'orderNumber' | 'accessToken' | 'status' | 'paymentStatus' | 'fulfillmentStatus' | 'processedAt' | 'confirmedAt' | 'cancelledAt' | 'expiredAt' | 'itemCount' | 'customerNote' | 'canCreatePayment' | 'paymentMethodType'> & {
|
|
5572
5574
|
totals: {
|
|
5573
5575
|
total: Pick<Money, 'amount' | 'currencyCode'>;
|
|
5574
5576
|
subtotal: Pick<Money, 'amount' | 'currencyCode'>;
|