@doswiftly/storefront-sdk 19.0.0 → 19.2.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/dist/core/client/create-client.d.ts.map +1 -1
  3. package/dist/core/client/create-client.js +8 -1
  4. package/dist/core/client/remote-debug-transport.d.ts +48 -0
  5. package/dist/core/client/remote-debug-transport.d.ts.map +1 -0
  6. package/dist/core/client/remote-debug-transport.js +198 -0
  7. package/dist/core/client/types.d.ts +51 -1
  8. package/dist/core/client/types.d.ts.map +1 -1
  9. package/dist/core/generated/operation-types.d.ts +31 -0
  10. package/dist/core/generated/operation-types.d.ts.map +1 -1
  11. package/dist/core/generated/operation-types.js +8 -0
  12. package/dist/core/index.d.ts +3 -1
  13. package/dist/core/index.d.ts.map +1 -1
  14. package/dist/core/index.js +3 -0
  15. package/dist/core/operations/cart.d.ts.map +1 -1
  16. package/dist/core/operations/cart.js +6 -0
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/react/cookies.d.ts +11 -1
  20. package/dist/react/cookies.d.ts.map +1 -1
  21. package/dist/react/cookies.js +11 -4
  22. package/dist/react/hooks/use-cart-manager.d.ts +7 -0
  23. package/dist/react/hooks/use-cart-manager.d.ts.map +1 -1
  24. package/dist/react/hooks/use-cart-manager.js +5 -2
  25. package/dist/react/providers/cart-manager-provider.d.ts +8 -1
  26. package/dist/react/providers/cart-manager-provider.d.ts.map +1 -1
  27. package/dist/react/providers/cart-manager-provider.js +2 -1
  28. package/dist/react/providers/storefront-provider.d.ts +12 -1
  29. package/dist/react/providers/storefront-provider.d.ts.map +1 -1
  30. package/dist/react/providers/storefront-provider.js +4 -3
  31. package/dist/react/stores/currency.store.d.ts +6 -1
  32. package/dist/react/stores/currency.store.d.ts.map +1 -1
  33. package/dist/react/stores/currency.store.js +49 -41
  34. package/dist/react/stores/language.store.d.ts +6 -1
  35. package/dist/react/stores/language.store.d.ts.map +1 -1
  36. package/dist/react/stores/language.store.js +46 -38
  37. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,143 @@
1
1
  # Changelog
2
2
 
3
+ ## 19.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b60dcaf: Discount codes now report real per-code applicability and reasons on the cart and order.
8
+
9
+ **Why**: a recognised discount code previously always showed `isApplicable: true` even when it
10
+ reduced nothing — minimum-order not met, code expired, or no cart items within the code's product
11
+ scope — and `discountAllocations` only ever reflected the first code. The contract now tells the
12
+ truth per code, so a storefront can show the buyer exactly which codes apply and why the others do
13
+ not, and the order carries the same breakdown the buyer saw in the cart.
14
+
15
+ **Additive (backward-compatible)**:
16
+ 1. `Cart.discountCodes[].isApplicable` is computed per code — `true` only when the code reduces the
17
+ price or grants free shipping — instead of being hard-coded to `true`.
18
+ 2. `Cart.discountAllocations` contains one entry per applicable code, not just the first.
19
+ 3. `cartDiscountCodesUpdate` returns a `warning` per non-applicable code
20
+ (`CartWarningCode.DISCOUNT_CODE_NOT_APPLICABLE`) whose localized message explains the reason. The
21
+ code stays on the cart, so adding a qualifying product can still activate it.
22
+ 4. New `DiscountErrorCode.NOT_APPLICABLE_TO_CART` — the code is valid but no cart items fall within
23
+ its product/collection/category scope.
24
+ 5. New `Order.discountAllocations` — the per-code breakdown frozen onto the order at checkout
25
+ (parity with `Cart.discountAllocations`), so a confirmation page renders the same rows.
26
+ 6. `cartValidateDiscountCode` (the read-only preview) now mirrors the apply result: a code that is
27
+ recognised but out of scope for the cart returns `isValid: false` with
28
+ `error.code: NOT_APPLICABLE_TO_CART`. Inline "is this code valid?" feedback no longer reports a code
29
+ as valid when applying it would reduce nothing — preview and apply always agree.
30
+
31
+ **Usage example**:
32
+
33
+ ```ts
34
+ const { cart, warnings } = await cartDiscountCodesUpdate({
35
+ id,
36
+ discountCodes: ["SUMMER20"],
37
+ });
38
+
39
+ for (const entry of cart.discountCodes) {
40
+ if (!entry.isApplicable) {
41
+ // The code is recognised but not reducing the price — explain why.
42
+ const reason = warnings.find((w) => w.target === entry.code)?.message;
43
+ showInlineHint(entry.code, reason);
44
+ }
45
+ }
46
+ ```
47
+
48
+ **Migration checklist for existing storefronts**:
49
+ - [ ] If you render a discount chip, branch on `discountCodes[].isApplicable` instead of assuming the code applied.
50
+ - [ ] Surface `cartDiscountCodesUpdate.warnings` (code `DISCOUNT_CODE_NOT_APPLICABLE`) next to the chip to tell the buyer why a code is inactive.
51
+ - [ ] Sum `discountAllocations[].amount` to display a per-code discount breakdown; it equals the cart/order discount total.
52
+ - [ ] Optionally read `Order.discountAllocations` on the confirmation page for the same breakdown after checkout.
53
+ - [ ] If you preview codes with `cartValidateDiscountCode`, handle `isValid: false` + `NOT_APPLICABLE_TO_CART` — don't show "valid" for a scope-mismatch code (the preview now matches what apply returns).
54
+
55
+ - d7b0185: Add `pickupConfig` to `AvailableShippingMethod` — render carrier pickup-point pickers at checkout
56
+
57
+ Shipping methods that deliver to a pickup point (`deliveryType` `LOCKER` or `PICKUP_POINT`) now expose a `pickupConfig` object with everything the storefront needs to let the buyer choose a collection point:
58
+ - `selectionMode: WIDGET` — render the carrier map widget. `widgetToken` (a public, domain-scoped browser token, e.g. the InPost Geowidget token — never a carrier API secret) and `scriptUrl` (the widget script to load) are provided.
59
+ - `selectionMode: SEARCH` — no browser widget; look points up server-side and render your own list. `widgetToken` and `scriptUrl` are `null` for this mode.
60
+
61
+ `pickupConfig` is `null` for `HOME` (street-address) delivery and when the merchant has not yet configured the carrier's public widget token — do not render a map in that case. The `AvailableShippingMethod` fragment now includes `pickupConfig`, so queries that spread it pick the new field up automatically once you regenerate your types.
62
+
63
+ Flow: render the picker from `pickupConfig`, then send the chosen point with `cartSetShippingAddress({ pickupPoint })` before `cartSelectShippingMethod` — the latter rejects a pickup method selected without a point (`PICKUP_POINT_REQUIRED`).
64
+
65
+ - 88cd617: Expose pickup-point configuration on shipping methods, so a storefront can render the carrier point picker directly from the cart's available shipping methods.
66
+
67
+ `availableShippingMethods` now returns a `pickupConfig` for methods whose `deliveryType` is `PICKUP_POINT` or `LOCKER`:
68
+ - `provider` — carrier code the config belongs to (e.g. `"inpost"`).
69
+ - `selectionMode` — `WIDGET` (render the carrier map) or `SEARCH` (query points server-side and render your own list).
70
+ - `widgetToken` — public, domain-scoped widget token (e.g. the InPost Geowidget token) used to initialise the map. **Never** a carrier API secret. Null in `SEARCH` mode and when the merchant has not configured the public token.
71
+ - `scriptUrl` — CDN URL of the carrier widget script to load before rendering the map. Null in `SEARCH` mode.
72
+
73
+ `pickupConfig` is `null` for `HOME` delivery methods.
74
+
75
+ **Additive (backward-compatible)** — existing code that does not read `pickupConfig` keeps working unchanged.
76
+
77
+ ## 19.1.0
78
+
79
+ ### Minor Changes
80
+
81
+ - 868894e: Add remote debug shipping for both GraphQL **and** cookie-lifecycle events, so you can trace a real shopper session end-to-end instead of reproducing it with DevTools open.
82
+
83
+ **Why**: the most useful checkout signal is often "when was the `cart-id` / currency / language cookie set or cleared?" (e.g. `cart-id` cleared by `complete()`), but debug output previously covered only GraphQL request/response. Now both flow through one channel keyed by a single `sessionId`, so one filter in your logs gives the full interleaved timeline.
84
+
85
+ **Additive (backward-compatible)** — everything is opt-in and off by default:
86
+ 1. `DebugOptions.remote?: boolean | RemoteDebugOptions | RemoteDebugSink` — ship debug events to a backend ingest endpoint. Pass a pre-built transport (a `RemoteDebugSink`) to share **one** channel/`sessionId` across the client and the cookie stores.
87
+ 2. New `createRemoteDebugTransport(...)` factory (+ `RemoteDebugTransport` / `RemoteDebugSink` / `RemoteDebugOptions` types).
88
+ 3. Cookie stores emit a new `DebugEvent` with `phase: 'cookie'` (`{ name, action: 'set' | 'clear', value?, maxAge? }`) when wired with a sink:
89
+ - `createBrowserCartCookieStore({ onDebug })` — `cart-id` (when you own the cookie store).
90
+ - `useCartManager({ cookieDebug })` / `<CartManagerProvider cookieDebug={...}>` — `cart-id` when the cart manager owns the cookie store.
91
+ - `createCurrencyStore({ onDebug })` / `createLanguageStore(initial, { onDebug })` — currency / language.
92
+ - `<StorefrontProvider cookieDebug={...}>` forwards the sink to the currency + language stores.
93
+ 4. `DebugOptions`, `DebugEvent`, `RemoteDebugOptions`, `RemoteDebugSink` are exported for typing.
94
+
95
+ **Usage example** (one shared channel across GraphQL + cookies):
96
+
97
+ ```ts
98
+ import {
99
+ createStorefrontClient,
100
+ createRemoteDebugTransport,
101
+ createBrowserCartCookieStore,
102
+ } from "@doswiftly/storefront-sdk";
103
+
104
+ const debugOn = process.env.NEXT_PUBLIC_SDK_DEBUG_REMOTE === "true";
105
+ const transport = debugOn
106
+ ? createRemoteDebugTransport({
107
+ endpoint: `${apiUrl}/storefront/debug-logs`,
108
+ shopSlug,
109
+ fetch: globalThis.fetch,
110
+ })
111
+ : null;
112
+
113
+ const client = createStorefrontClient({
114
+ apiUrl,
115
+ shopSlug,
116
+ debug: transport
117
+ ? { userErrors: true, response: true, timing: true, remote: transport }
118
+ : false,
119
+ });
120
+
121
+ // cart-id set/clear on the same channel:
122
+ const cartCookieStore = createBrowserCartCookieStore(
123
+ transport ? { onDebug: (e) => transport.capture(e) } : undefined,
124
+ );
125
+
126
+ // currency + language via the provider:
127
+ // <StorefrontProvider cookieDebug={transport ? (e) => transport.capture(e) : undefined} … />
128
+ ```
129
+
130
+ Behaviour:
131
+ - **Batched** (default 10 events / 5s); `keepalive` is used on page-hide/unload flushes so trailing events survive a navigation or payment-gateway redirect.
132
+ - **Fire-and-forget**: a failed send is swallowed — telemetry can never break the request that produced it.
133
+ - Cookies are **not** sent (`credentials: 'omit'`); the shop is identified by the `X-Shop-Slug` header.
134
+ - **Redaction**: the SDK masks `Authorization` / `customerAccessToken` in _headers_. It does **not** redact request `variables` or response bodies — the backend ingest endpoint masks emails + credential-named fields. Enable `response` / `headers` only for operations whose payload you're comfortable sending to your backend.
135
+ - Default endpoint is `${apiUrl}/storefront/debug-logs`; override via `RemoteDebugOptions.endpoint`.
136
+
137
+ **Migration checklist for existing storefronts**:
138
+ - [ ] No action required — all of the above is opt-in and defaults to off.
139
+ - [ ] To adopt: build one transport (gated behind an env flag), pass it to `createStorefrontClient`, the cart cookie store, and `<StorefrontProvider cookieDebug>`, then confirm requests to `/storefront/debug-logs`.
140
+
3
141
  ## 19.0.0
4
142
 
5
143
  ### Major Changes
@@ -1 +1 @@
1
- {"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../../../src/core/client/create-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAKjB,MAAM,SAAS,CAAC;AAOjB,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,gBAAgB,CA0GvF"}
1
+ {"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../../../src/core/client/create-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAKjB,MAAM,SAAS,CAAC;AAQjB,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,gBAAgB,CAgHvF"}
@@ -11,6 +11,7 @@
11
11
  * 0 runtime dependencies.
12
12
  */
13
13
  import { createExecute, resolveDebugOptions } from './execute';
14
+ import { maybeAttachRemoteTransport } from './remote-debug-transport';
14
15
  import { compose } from './compose';
15
16
  import { dedupe } from './dedupe';
16
17
  import { hashQuery } from './hash';
@@ -24,7 +25,13 @@ export function createStorefrontClient(config) {
24
25
  // Normalise the public `boolean | 'verbose' | DebugOptions` union into the
25
26
  // canonical ResolvedDebugOptions shape (or null = no logging). Env var
26
27
  // fallback (`DOSWIFTLY_SDK_DEBUG`) applies only when `debug` is undefined.
27
- const resolvedDebug = resolveDebugOptions(debug);
28
+ // When `debug.remote` is set, the resolved sink is wrapped so every event is
29
+ // also shipped to the backend ingest endpoint (default `${apiUrl}/storefront/debug-logs`).
30
+ const resolvedDebug = maybeAttachRemoteTransport(resolveDebugOptions(debug), debug, {
31
+ apiUrl,
32
+ shopSlug,
33
+ fetch: customFetch,
34
+ });
28
35
  // Create the innermost execute function (native fetch)
29
36
  const innerExecute = createExecute({ endpoint, fetch: customFetch, debug: resolvedDebug });
30
37
  /**
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Remote debug transport — batches debug events and ships them to a backend ingest
3
+ * endpoint over `fetch`. 0 runtime dependencies (only `fetch`, `Date`, `globalThis.crypto`).
4
+ *
5
+ * Fire-and-forget by contract: a failed flush is swallowed so telemetry can never break the
6
+ * storefront request that produced the event. The buffer flushes when it reaches `batchSize`,
7
+ * after `flushIntervalMs`, or when the page is hidden / unloading (browser only) — the last one
8
+ * matters for redirect-heavy flows (e.g. payment gateway hand-off) where trailing events would
9
+ * otherwise be lost. `fetch(..., { keepalive: true })` lets that final send complete during unload.
10
+ *
11
+ * Credentials are never sent (`credentials: 'omit'`) — this is telemetry, not an authenticated
12
+ * call. The shop is identified by the `X-Shop-Slug` header; the backend validates it.
13
+ */
14
+ import type { DebugEvent, DebugOptions } from './types';
15
+ import type { ResolvedDebugOptions } from './execute';
16
+ export interface RemoteDebugTransportConfig {
17
+ /** Fully-resolved ingest endpoint URL. */
18
+ endpoint: string;
19
+ /** Shop slug sent as `X-Shop-Slug` so the backend can resolve the tenant. */
20
+ shopSlug: string;
21
+ /** Fetch implementation (the same one the client transport uses). */
22
+ fetch: typeof globalThis.fetch;
23
+ batchSize?: number;
24
+ flushIntervalMs?: number;
25
+ sessionId?: string;
26
+ sdkVersion?: string;
27
+ }
28
+ export interface RemoteDebugTransport {
29
+ /** Buffer one debug event; flushes automatically per the batching policy. */
30
+ capture(event: DebugEvent): void;
31
+ }
32
+ export declare function createRemoteDebugTransport(config: RemoteDebugTransportConfig): RemoteDebugTransport;
33
+ /**
34
+ * Build a `phase: 'cookie'` {@link DebugEvent} for a cookie set/clear. Single construction point so
35
+ * the cart / currency / language stores emit an identical shape (`{ name, action, value?, maxAge? }`).
36
+ */
37
+ export declare function cookieDebugEvent(name: string, action: 'set' | 'clear', value?: string, maxAge?: number): DebugEvent;
38
+ /**
39
+ * If `debug.remote` is enabled, wrap the resolved debug sink so every event is also handed to a
40
+ * remote transport. Returns the input untouched when remote is off (or debug is disabled), so the
41
+ * local `log` behaviour is preserved exactly.
42
+ */
43
+ export declare function maybeAttachRemoteTransport(resolved: ResolvedDebugOptions | null, debugConfig: boolean | 'verbose' | DebugOptions | undefined, context: {
44
+ apiUrl: string;
45
+ shopSlug: string;
46
+ fetch: typeof globalThis.fetch;
47
+ }): ResolvedDebugOptions | null;
48
+ //# sourceMappingURL=remote-debug-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-debug-transport.d.ts","sourceRoot":"","sources":["../../../src/core/client/remote-debug-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAuC,MAAM,SAAS,CAAC;AAC7F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAMtD,MAAM,WAAW,0BAA0B;IACzC,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAClC;AAuDD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,0BAA0B,GAAG,oBAAoB,CAmFnG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,KAAK,GAAG,OAAO,EACvB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,CAKZ;AAOD;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,EACrC,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,EAC3D,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAAE,GAC5E,oBAAoB,GAAG,IAAI,CA0C7B"}
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Remote debug transport — batches debug events and ships them to a backend ingest
3
+ * endpoint over `fetch`. 0 runtime dependencies (only `fetch`, `Date`, `globalThis.crypto`).
4
+ *
5
+ * Fire-and-forget by contract: a failed flush is swallowed so telemetry can never break the
6
+ * storefront request that produced the event. The buffer flushes when it reaches `batchSize`,
7
+ * after `flushIntervalMs`, or when the page is hidden / unloading (browser only) — the last one
8
+ * matters for redirect-heavy flows (e.g. payment gateway hand-off) where trailing events would
9
+ * otherwise be lost. `fetch(..., { keepalive: true })` lets that final send complete during unload.
10
+ *
11
+ * Credentials are never sent (`credentials: 'omit'`) — this is telemetry, not an authenticated
12
+ * call. The shop is identified by the `X-Shop-Slug` header; the backend validates it.
13
+ */
14
+ import { CART_COOKIE_NAME } from '../cart/cookie-config';
15
+ const DEFAULT_BATCH_SIZE = 10;
16
+ const DEFAULT_FLUSH_INTERVAL_MS = 5000;
17
+ /**
18
+ * Pull identifying ids out of operation variables so the backend log is filterable by cart /
19
+ * order without parsing the whole payload. `cartId` comes from an explicit `cartId` variable, or
20
+ * from the `id` variable on cart operations (cart mutations pass the cart as `id: cartId`). The
21
+ * operation-name check is anchored (`^cart`) so only cart operations (`CartAddLines`, …) qualify —
22
+ * an unrelated op that merely contains "cart" (e.g. `ProductInCartBadge`) won't mislabel its `id`.
23
+ */
24
+ function extractIdentifiers(event) {
25
+ // Cookie events carry the value directly — surface the cart-id so its set/clear correlates with
26
+ // the cart's GraphQL operations under one `cartId` filter.
27
+ if (event.phase === 'cookie') {
28
+ const cookie = event.data;
29
+ return cookie.name === CART_COOKIE_NAME && typeof cookie.value === 'string'
30
+ ? { cartId: cookie.value }
31
+ : {};
32
+ }
33
+ const variables = event.data.variables;
34
+ if (!variables || typeof variables !== 'object')
35
+ return {};
36
+ const v = variables;
37
+ const op = event.operationName ?? '';
38
+ const out = {};
39
+ const cartId = typeof v.cartId === 'string'
40
+ ? v.cartId
41
+ : /^cart/i.test(op) && typeof v.id === 'string'
42
+ ? v.id
43
+ : undefined;
44
+ if (cartId)
45
+ out.cartId = cartId;
46
+ if (typeof v.orderId === 'string')
47
+ out.orderId = v.orderId;
48
+ return out;
49
+ }
50
+ /**
51
+ * Generate a session correlation id. Prefers `crypto.randomUUID`; falls back to a
52
+ * timestamp + random suffix when unavailable (uniqueness within one client is all that is
53
+ * required here — there is no security property to preserve).
54
+ */
55
+ function generateSessionId() {
56
+ const c = globalThis.crypto;
57
+ if (c && typeof c.randomUUID === 'function')
58
+ return c.randomUUID();
59
+ return `sdk-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
60
+ }
61
+ export function createRemoteDebugTransport(config) {
62
+ const { endpoint, shopSlug, fetch: fetchFn, batchSize = DEFAULT_BATCH_SIZE, flushIntervalMs = DEFAULT_FLUSH_INTERVAL_MS, sdkVersion, } = config;
63
+ const sessionId = config.sessionId ?? generateSessionId();
64
+ let buffer = [];
65
+ let timer = null;
66
+ function clearFlushTimer() {
67
+ if (timer !== null) {
68
+ clearTimeout(timer);
69
+ timer = null;
70
+ }
71
+ }
72
+ function flush(useKeepalive = false) {
73
+ if (buffer.length === 0)
74
+ return;
75
+ const events = buffer;
76
+ buffer = [];
77
+ clearFlushTimer();
78
+ // Fire-and-forget — a transport failure must never surface to the caller.
79
+ void Promise.resolve()
80
+ .then(() => fetchFn(endpoint, {
81
+ method: 'POST',
82
+ headers: {
83
+ 'Content-Type': 'application/json',
84
+ 'X-Shop-Slug': shopSlug,
85
+ },
86
+ body: JSON.stringify({ sessionId, sdkVersion, events }),
87
+ // `keepalive` only for unload flushes — on a live page a large batch can exceed the
88
+ // browser's keepalive body quota (~64KB) and be rejected (then silently swallowed),
89
+ // whereas an ordinary fetch has no such cap. Unload flushes need it to survive navigation.
90
+ keepalive: useKeepalive,
91
+ credentials: 'omit',
92
+ }))
93
+ .catch(() => undefined);
94
+ }
95
+ // Flush on page hide / unload so trailing events survive a navigation or gateway redirect.
96
+ // Browser-only — guarded so the core stays usable in Node / Edge / Deno. These listeners live for
97
+ // the transport's lifetime (no teardown): the storefront client is expected to be created once per
98
+ // app (the SDK's singleton/Context convention), so they are registered once. Do not build a new
99
+ // client with `debug.remote` on every render — that would accumulate listeners.
100
+ const g = globalThis;
101
+ if (typeof g.addEventListener === 'function') {
102
+ g.addEventListener('pagehide', () => flush(true));
103
+ g.addEventListener('visibilitychange', () => {
104
+ if (g.document?.visibilityState === 'hidden')
105
+ flush(true);
106
+ });
107
+ }
108
+ return {
109
+ capture(event) {
110
+ buffer.push({
111
+ timestamp: new Date().toISOString(),
112
+ phase: event.phase,
113
+ operationName: event.operationName ?? null,
114
+ ...extractIdentifiers(event),
115
+ data: event.data,
116
+ });
117
+ if (buffer.length >= batchSize) {
118
+ flush();
119
+ }
120
+ else if (timer === null) {
121
+ timer = setTimeout(() => flush(), flushIntervalMs);
122
+ // Don't keep a Node/Edge event loop alive for a pending flush (browser timers are numbers
123
+ // with no `unref`; Node/Edge timers are objects that have it).
124
+ if (timer && typeof timer === 'object' && 'unref' in timer) {
125
+ timer.unref();
126
+ }
127
+ }
128
+ },
129
+ };
130
+ }
131
+ /**
132
+ * Build a `phase: 'cookie'` {@link DebugEvent} for a cookie set/clear. Single construction point so
133
+ * the cart / currency / language stores emit an identical shape (`{ name, action, value?, maxAge? }`).
134
+ */
135
+ export function cookieDebugEvent(name, action, value, maxAge) {
136
+ const data = { name, action };
137
+ if (value !== undefined)
138
+ data.value = value;
139
+ if (maxAge !== undefined)
140
+ data.maxAge = maxAge;
141
+ return { phase: 'cookie', operationName: undefined, data };
142
+ }
143
+ /** True when `remote` is a pre-built sink (has a callable `capture`) rather than options/boolean. */
144
+ function isRemoteSink(remote) {
145
+ return typeof remote === 'object' && typeof remote.capture === 'function';
146
+ }
147
+ /**
148
+ * If `debug.remote` is enabled, wrap the resolved debug sink so every event is also handed to a
149
+ * remote transport. Returns the input untouched when remote is off (or debug is disabled), so the
150
+ * local `log` behaviour is preserved exactly.
151
+ */
152
+ export function maybeAttachRemoteTransport(resolved, debugConfig, context) {
153
+ if (!resolved)
154
+ return resolved;
155
+ // `remote` only exists on the object form of `debug` (`DebugOptions`).
156
+ if (typeof debugConfig !== 'object' || !debugConfig.remote) {
157
+ return resolved;
158
+ }
159
+ const remote = debugConfig.remote;
160
+ // A pre-built sink (e.g. a transport shared with the cookie stores) is used as-is, so the whole
161
+ // app reports through one channel + sessionId. Otherwise build a transport from the options.
162
+ let transport;
163
+ if (isRemoteSink(remote)) {
164
+ transport = remote;
165
+ }
166
+ else {
167
+ const remoteOpts = remote === true ? {} : remote;
168
+ transport = createRemoteDebugTransport({
169
+ endpoint: remoteOpts.endpoint ?? `${context.apiUrl.replace(/\/$/, '')}/storefront/debug-logs`,
170
+ shopSlug: context.shopSlug,
171
+ fetch: context.fetch,
172
+ batchSize: remoteOpts.batchSize,
173
+ flushIntervalMs: remoteOpts.flushIntervalMs,
174
+ sessionId: remoteOpts.sessionId,
175
+ sdkVersion: remoteOpts.sdkVersion,
176
+ });
177
+ }
178
+ const originalLog = resolved.log;
179
+ return {
180
+ ...resolved,
181
+ // Telemetry must never break the request: `execute` calls `debug.log` synchronously outside any
182
+ // try/catch, so a throwing local sink or remote `capture` is swallowed here.
183
+ log: (event) => {
184
+ try {
185
+ originalLog(event);
186
+ }
187
+ catch {
188
+ /* local sink threw — ignore */
189
+ }
190
+ try {
191
+ transport.capture(event);
192
+ }
193
+ catch {
194
+ /* remote sink threw — ignore */
195
+ }
196
+ },
197
+ };
198
+ }
@@ -110,14 +110,64 @@ export interface DebugOptions {
110
110
  userErrors?: boolean;
111
111
  /** Custom logger sink. Default `console.log('[StorefrontSDK]', event.phase, event.operationName, event.data)`. Use for routing into `pino` / `winston`. */
112
112
  log?: (event: DebugEvent) => void;
113
+ /**
114
+ * Ship debug events to a remote ingest endpoint, in addition to the local `log` sink.
115
+ *
116
+ * Pass `true` to use defaults, or a `RemoteDebugOptions` object to customise the endpoint and
117
+ * batching. Events are buffered and sent with `fetch` (with `keepalive` on page-unload flushes so
118
+ * trailing events survive a navigation); a failed send is swallowed so telemetry can never break
119
+ * the request that produced it. Cookies are not sent (`credentials: 'omit'`). Default `false`.
120
+ *
121
+ * Redaction note: the SDK masks `Authorization` / `customerAccessToken` in *headers*. It does NOT
122
+ * redact the request `variables` or response body it ships — the backend ingest endpoint is the
123
+ * safety net there (it masks emails and credential-named fields). Enable `response` / `headers`
124
+ * only for operations whose payload you are comfortable sending to your backend.
125
+ *
126
+ * Which events are shipped is governed by the other flags above — `remote: true` alone ships the
127
+ * minimal set (request variables + response status + `userErrors`); enable `response` / `headers`
128
+ * / `timing` to ship more.
129
+ *
130
+ * Pass a pre-built transport (a `RemoteDebugSink`, e.g. from `createRemoteDebugTransport`) to
131
+ * share ONE channel + sessionId across the GraphQL client and the cookie stores, so a single
132
+ * `sessionId` in your logs gives the full interleaved timeline (GraphQL + cookie set/clear).
133
+ */
134
+ remote?: boolean | RemoteDebugOptions | RemoteDebugSink;
135
+ }
136
+ /**
137
+ * Configuration for the remote debug transport (`DebugOptions.remote`). All fields optional.
138
+ */
139
+ export interface RemoteDebugOptions {
140
+ /** Ingest endpoint URL. Default `${apiUrl}/storefront/debug-logs`. */
141
+ endpoint?: string;
142
+ /** Flush the buffer once this many events accumulate. Default `10`. */
143
+ batchSize?: number;
144
+ /** Flush the buffer after this many milliseconds even if `batchSize` is not reached. Default `5000`. */
145
+ flushIntervalMs?: number;
146
+ /** Correlation id grouping every event from one client instance. Default a generated id. */
147
+ sessionId?: string;
148
+ /** Optional client/build version label attached to every batch. */
149
+ sdkVersion?: string;
150
+ }
151
+ /**
152
+ * Minimal surface of a debug transport — accepts already-built `DebugEvent`s. A `RemoteDebugSink`
153
+ * created once (via `createRemoteDebugTransport`) and shared between the GraphQL client and the
154
+ * cookie stores forms a single debug channel with one `sessionId`.
155
+ */
156
+ export interface RemoteDebugSink {
157
+ capture(event: DebugEvent): void;
113
158
  }
114
159
  /**
115
160
  * Structured event handed to a custom `DebugOptions.log` sink. Stable contract:
116
161
  * `phase` and `operationName` are always present; `data` carries the merged
117
162
  * payload selected by `DebugOptions` flags.
163
+ *
164
+ * `phase: 'cookie'` events come from the SDK cookie layer (cart / currency / language) rather than
165
+ * the GraphQL transport — `operationName` is `undefined` and `data` is
166
+ * `{ name, action: 'set' | 'clear', value?, maxAge? }`. They let you see exactly when a cookie was
167
+ * written or removed (e.g. `cart-id` cleared by `complete()`) alongside the GraphQL timeline.
118
168
  */
119
169
  export interface DebugEvent {
120
- phase: 'request' | 'response';
170
+ phase: 'request' | 'response' | 'cookie';
121
171
  operationName: string | undefined;
122
172
  data: Record<string, unknown>;
123
173
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/client/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/client/types.ts"],"names":[],"mappings":"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"}
@@ -254,6 +254,8 @@ export type AvailableShippingMethod = {
254
254
  isFree: Scalars['Boolean']['output'];
255
255
  /** Display name of the method (e.g. "DPD Standard"). */
256
256
  name: Scalars['String']['output'];
257
+ /** Pickup-point selection config — present only for pickup methods (deliveryType LOCKER / PICKUP_POINT) whose carrier exposes a public pickup mechanism. Null for HOME delivery and when the merchant has not configured the carrier's public widget token. Carries only browser-safe data (e.g. the InPost Geowidget public token), never the carrier API secret. */
258
+ pickupConfig?: Maybe<ShippingPickupConfig>;
257
259
  /** Cost of the method for the current cart (in the buyer preferred currency). */
258
260
  price: Money;
259
261
  /** Merchant-configured display position — lower values come first in the picker. */
@@ -997,6 +999,7 @@ export type CartWarning = {
997
999
  target: Scalars['String']['output'];
998
1000
  };
999
1001
  export declare const CartWarningCode: {
1002
+ readonly DiscountCodeNotApplicable: "DISCOUNT_CODE_NOT_APPLICABLE";
1000
1003
  readonly MerchandiseNotAvailable: "MERCHANDISE_NOT_AVAILABLE";
1001
1004
  readonly MerchandiseNotEnoughStock: "MERCHANDISE_NOT_ENOUGH_STOCK";
1002
1005
  readonly PaymentsAmountRegionMismatch: "PAYMENTS_AMOUNT_REGION_MISMATCH";
@@ -1587,6 +1590,7 @@ export declare const DiscountErrorCode: {
1587
1590
  readonly Inactive: "INACTIVE";
1588
1591
  readonly MinimumOrderNotMet: "MINIMUM_ORDER_NOT_MET";
1589
1592
  readonly MinimumQuantityNotMet: "MINIMUM_QUANTITY_NOT_MET";
1593
+ readonly NotApplicableToCart: "NOT_APPLICABLE_TO_CART";
1590
1594
  readonly NotFound: "NOT_FOUND";
1591
1595
  readonly NotStarted: "NOT_STARTED";
1592
1596
  readonly ShopNotFound: "SHOP_NOT_FOUND";
@@ -2343,6 +2347,8 @@ export type Order = Node & {
2343
2347
  cancelledAt?: Maybe<Scalars['DateTime']['output']>;
2344
2348
  /** When the order was confirmed (e.g. payment authorised / approved). Null until confirmation. */
2345
2349
  confirmedAt?: Maybe<Scalars['DateTime']['output']>;
2350
+ /** 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. */
2351
+ discountAllocations: Array<OrderDiscountAllocation>;
2346
2352
  /** When the order expired (e.g. pending payment timed out). Null when not expired. */
2347
2353
  expiredAt?: Maybe<Scalars['DateTime']['output']>;
2348
2354
  /** Fulfillment progress (UNFULFILLED, PARTIALLY_FULFILLED, FULFILLED, IN_TRANSIT, DELIVERED, PARTIALLY_RETURNED, RETURNED, LOST). */
@@ -2380,6 +2386,12 @@ export type OrderConnection = {
2380
2386
  /** Total orders count */
2381
2387
  totalCount: Scalars['Int']['output'];
2382
2388
  };
2389
+ export type OrderDiscountAllocation = {
2390
+ /** Amount discounted by this code on the order, in the order currency. */
2391
+ amount: Money;
2392
+ /** The discount code that produced this allocation. */
2393
+ discountCode: Scalars['String']['output'];
2394
+ };
2383
2395
  export type OrderEdge = {
2384
2396
  /** Cursor */
2385
2397
  cursor: Scalars['String']['output'];
@@ -2637,6 +2649,13 @@ export type PickupPointInput = {
2637
2649
  /** Courier network code (e.g. inpost, orlen, dpd) */
2638
2650
  provider: Scalars['String']['input'];
2639
2651
  };
2652
+ export declare const PickupSelectionMode: {
2653
+ /** No browser widget — query points server-side (by city / postal code) and render your own list. `widgetToken` / `scriptUrl` are null for this mode. */
2654
+ readonly Search: "SEARCH";
2655
+ /** Render the carrier map widget (e.g. InPost Geowidget) initialised with `pickupConfig.widgetToken` + `pickupConfig.scriptUrl`. The buyer picks a point on the map. */
2656
+ readonly Widget: "WIDGET";
2657
+ };
2658
+ export type PickupSelectionMode = typeof PickupSelectionMode[keyof typeof PickupSelectionMode];
2640
2659
  export type PointsEstimate = {
2641
2660
  /** Base points to earn */
2642
2661
  basePoints: Scalars['Int']['output'];
@@ -3456,6 +3475,16 @@ export type ShippingCarrier = {
3456
3475
  /** Carrier service code as configured by the merchant (e.g. "inpost_paczkomat"). Useful when integrating carrier widgets. */
3457
3476
  serviceCode?: Maybe<Scalars['String']['output']>;
3458
3477
  };
3478
+ export type ShippingPickupConfig = {
3479
+ /** Carrier code this config belongs to (e.g. "inpost"). Matches `carrier.serviceCode`'s provider. */
3480
+ provider: Scalars['String']['output'];
3481
+ /** CDN URL of the carrier widget script to load before rendering the map. Null for SEARCH mode. */
3482
+ scriptUrl?: Maybe<Scalars['String']['output']>;
3483
+ /** How to let the buyer pick a point — WIDGET (carrier map) or SEARCH (server-side point lookup). */
3484
+ selectionMode: PickupSelectionMode;
3485
+ /** PUBLIC widget token used to initialise the carrier map (InPost Geowidget domain-scoped token). Never the carrier API secret. Null for SEARCH mode and when the merchant has not configured the public token (do not render the map — offer SEARCH or hide the method). */
3486
+ widgetToken?: Maybe<Scalars['String']['output']>;
3487
+ };
3459
3488
  export type Shop = {
3460
3489
  /** Shop physical address */
3461
3490
  address?: Maybe<ShopAddress>;
@@ -3980,6 +4009,7 @@ export type CartAvailableShippingMethodsQuery = {
3980
4009
  cart?: Maybe<(Pick<Cart, 'id' | 'requiresShipping'> & {
3981
4010
  availableShippingMethods: {
3982
4011
  methods: Array<(Pick<AvailableShippingMethod, 'id' | 'name' | 'description' | 'deliveryType' | 'isFree' | 'sortOrder'> & {
4012
+ pickupConfig?: Maybe<Pick<ShippingPickupConfig, 'provider' | 'selectionMode' | 'widgetToken' | 'scriptUrl'>>;
3983
4013
  carrier?: Maybe<(Pick<ShippingCarrier, 'id' | 'name' | 'serviceCode'> & {
3984
4014
  logo?: Maybe<Pick<Image, 'id' | 'url' | 'altText' | 'width' | 'height' | 'thumbhash'>>;
3985
4015
  })>;
@@ -5227,6 +5257,7 @@ export type PageInfoFragment = Pick<PageInfo, 'hasNextPage' | 'hasPreviousPage'
5227
5257
  export type UserErrorFragment = Pick<UserError, 'message' | 'code' | 'field'>;
5228
5258
  export type CartWarningFragment = Pick<CartWarning, 'message' | 'code' | 'target'>;
5229
5259
  export type AvailableShippingMethodFragment = (Pick<AvailableShippingMethod, 'id' | 'name' | 'description' | 'deliveryType' | 'isFree' | 'sortOrder'> & {
5260
+ pickupConfig?: Maybe<Pick<ShippingPickupConfig, 'provider' | 'selectionMode' | 'widgetToken' | 'scriptUrl'>>;
5230
5261
  carrier?: Maybe<(Pick<ShippingCarrier, 'id' | 'name' | 'serviceCode'> & {
5231
5262
  logo?: Maybe<Pick<Image, 'id' | 'url' | 'altText' | 'width' | 'height' | 'thumbhash'>>;
5232
5263
  })>;