@vtex/faststore 0.0.8 → 0.0.10

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 (151) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/actions.json +42 -0
  3. package/dist/catalog.json +731 -16
  4. package/dist/cli/index.js +1 -1
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/components.d.ts +9 -4
  7. package/dist/components.js +11 -6
  8. package/dist/content-types.json +300 -0
  9. package/dist/core/dist/define-component.d.ts +6 -1
  10. package/dist/docs/account-entry-point.md +4 -4
  11. package/dist/docs/account-menu.md +2 -2
  12. package/dist/docs/account-saved-cards.md +301 -0
  13. package/dist/docs/binding-switcher.md +154 -0
  14. package/dist/docs/card-entry.md +3 -3
  15. package/dist/docs/card-summary.md +175 -0
  16. package/dist/docs/cart-destination.md +238 -0
  17. package/dist/docs/cart-item.md +21 -3
  18. package/dist/docs/cart-totalizer.md +1 -1
  19. package/dist/docs/cart.md +118 -11
  20. package/dist/docs/footer-social.md +1 -1
  21. package/dist/docs/free-shipping-progress.md +13 -9
  22. package/dist/docs/incentives.md +2 -2
  23. package/dist/docs/logo.md +1 -1
  24. package/dist/docs/payment-method-badges.md +2 -2
  25. package/dist/docs/payment-methods.md +1 -1
  26. package/dist/docs/product-information.md +2 -2
  27. package/dist/docs/product-shelf.md +7 -0
  28. package/dist/docs/select.md +3 -4
  29. package/dist/docs/sign-in-redirect.md +1 -1
  30. package/dist/docs/toast.md +30 -12
  31. package/dist/index.d.ts +2 -1
  32. package/dist/internal/client.d.ts +4 -1
  33. package/dist/internal/client.js +4 -1
  34. package/dist/internal/server.d.ts +4 -2
  35. package/dist/internal/server.js +4 -2
  36. package/dist/matter/account-saved-cards/account-saved-cards.d.ts +383 -0
  37. package/dist/matter/account-saved-cards/account-saved-cards.js +956 -0
  38. package/dist/matter/account-saved-cards/account-saved-cards.js.map +1 -0
  39. package/dist/matter/billing-address/billing-address.js.map +1 -1
  40. package/dist/matter/cart/cart-namespace.d.ts +5 -1
  41. package/dist/matter/cart/cart-namespace.js +5 -1
  42. package/dist/matter/cart/cart-namespace.js.map +1 -1
  43. package/dist/matter/cart/cart.d.ts +34 -4
  44. package/dist/matter/cart/cart.js +238 -28
  45. package/dist/matter/cart/cart.js.map +1 -1
  46. package/dist/matter/cart-destination/cart-destination.d.ts +222 -0
  47. package/dist/matter/cart-destination/cart-destination.js +613 -0
  48. package/dist/matter/cart-destination/cart-destination.js.map +1 -0
  49. package/dist/matter/cart-item/cart-item.d.ts +4 -2
  50. package/dist/matter/cart-item/cart-item.js +75 -16
  51. package/dist/matter/cart-item/cart-item.js.map +1 -1
  52. package/dist/matter/cart-totalizer/cart-totalizer.js +3 -2
  53. package/dist/matter/cart-totalizer/cart-totalizer.js.map +1 -1
  54. package/dist/matter/free-shipping-progress/free-shipping-progress.d.ts +6 -3
  55. package/dist/matter/free-shipping-progress/free-shipping-progress.js +76 -33
  56. package/dist/matter/free-shipping-progress/free-shipping-progress.js.map +1 -1
  57. package/dist/matter/pricing/pricing.js.map +1 -1
  58. package/dist/matter/product-shelf/product-shelf.d.ts +7 -0
  59. package/dist/matter/product-shelf/product-shelf.js +115 -1
  60. package/dist/matter/product-shelf/product-shelf.js.map +1 -1
  61. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  62. package/dist/matter/shipping-simulator/shipping-simulator.d.ts +1 -1
  63. package/dist/matter/shipping-simulator/shipping-simulator.js +6 -2
  64. package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
  65. package/dist/particles/binding-switcher/binding-switcher.d.ts +38 -0
  66. package/dist/particles/binding-switcher/binding-switcher.js +183 -0
  67. package/dist/particles/binding-switcher/binding-switcher.js.map +1 -0
  68. package/dist/particles/card-summary/card-network.d.ts +17 -0
  69. package/dist/particles/card-summary/card-network.js +25 -0
  70. package/dist/particles/card-summary/card-network.js.map +1 -0
  71. package/dist/particles/card-summary/card-summary.d.ts +83 -0
  72. package/dist/particles/card-summary/card-summary.js +188 -0
  73. package/dist/particles/card-summary/card-summary.js.map +1 -0
  74. package/dist/particles/footer-social/footer-social.d.ts +1 -1
  75. package/dist/particles/footer-social/footer-social.js +1 -1
  76. package/dist/particles/footer-social/footer-social.js.map +1 -1
  77. package/dist/particles/incentives/incentives.d.ts +1 -1
  78. package/dist/particles/incentives/incentives.js +1 -1
  79. package/dist/particles/incentives/incentives.js.map +1 -1
  80. package/dist/particles/payment-methods/payment-methods.d.ts +1 -1
  81. package/dist/particles/payment-methods/payment-methods.js +1 -1
  82. package/dist/particles/payment-methods/payment-methods.js.map +1 -1
  83. package/dist/particles/toast/toast.d.ts +20 -6
  84. package/dist/particles/toast/toast.js +49 -7
  85. package/dist/particles/toast/toast.js.map +1 -1
  86. package/dist/schema.graphql +298 -34
  87. package/dist/sdk/actions/index.d.ts +3 -1
  88. package/dist/sdk/actions/index.js +3 -1
  89. package/dist/sdk/actions/locale-action-provider.d.ts +16 -0
  90. package/dist/sdk/actions/locale-action-provider.js +20 -0
  91. package/dist/sdk/actions/locale-action-provider.js.map +1 -0
  92. package/dist/sdk/actions/saved-card-action-provider.d.ts +12 -0
  93. package/dist/sdk/actions/saved-card-action-provider.js +16 -0
  94. package/dist/sdk/actions/saved-card-action-provider.js.map +1 -0
  95. package/dist/sdk/actions/types.d.ts +6 -2
  96. package/dist/sdk/cache/storefront-cache.js +2 -1
  97. package/dist/sdk/cache/storefront-cache.js.map +1 -1
  98. package/dist/sdk/cart/operations.d.ts +2 -1
  99. package/dist/sdk/cart/operations.js +55 -1
  100. package/dist/sdk/cart/operations.js.map +1 -1
  101. package/dist/sdk/cart/optimistic.d.ts +30 -7
  102. package/dist/sdk/cart/optimistic.js +78 -41
  103. package/dist/sdk/cart/optimistic.js.map +1 -1
  104. package/dist/sdk/cart/use-cart-actions.d.ts +7 -0
  105. package/dist/sdk/cart/use-cart-actions.js +61 -15
  106. package/dist/sdk/cart/use-cart-actions.js.map +1 -1
  107. package/dist/sdk/cart/use-cart-sync-sales-channel.js +70 -0
  108. package/dist/sdk/cart/use-cart-sync-sales-channel.js.map +1 -0
  109. package/dist/sdk/generated/gql.js +15 -11
  110. package/dist/sdk/generated/gql.js.map +1 -1
  111. package/dist/sdk/generated/graphql.d.ts +177 -46
  112. package/dist/sdk/generated/graphql.js +2314 -93
  113. package/dist/sdk/generated/graphql.js.map +1 -1
  114. package/dist/sdk/i18n/messages/en-US.js +19 -0
  115. package/dist/sdk/i18n/messages/en-US.json +19 -0
  116. package/dist/sdk/i18n/messages/en-US.json.d.ts +19 -0
  117. package/dist/sdk/i18n/messages/es-ES.js +19 -0
  118. package/dist/sdk/i18n/messages/es-ES.json +19 -0
  119. package/dist/sdk/i18n/messages/pt-BR.js +19 -0
  120. package/dist/sdk/i18n/messages/pt-BR.json +19 -0
  121. package/dist/sdk/i18n/provider.js +1 -1
  122. package/dist/sdk/i18n/provider.js.map +1 -1
  123. package/dist/sdk/locale/binding-switch-provider.d.ts +13 -0
  124. package/dist/sdk/locale/binding-switch-provider.js +20 -0
  125. package/dist/sdk/locale/binding-switch-provider.js.map +1 -0
  126. package/dist/sdk/locale/use-locale-actions.d.ts +16 -0
  127. package/dist/sdk/locale/use-locale-actions.js +70 -0
  128. package/dist/sdk/locale/use-locale-actions.js.map +1 -0
  129. package/dist/sdk/navigation/binding-switch.d.ts +39 -0
  130. package/dist/sdk/navigation/binding-switch.js +129 -0
  131. package/dist/sdk/navigation/binding-switch.js.map +1 -0
  132. package/dist/sdk/navigation/decode-route-params.d.ts +11 -0
  133. package/dist/sdk/navigation/decode-route-params.js +32 -0
  134. package/dist/sdk/navigation/decode-route-params.js.map +1 -0
  135. package/dist/sdk/saved-cards/operations.d.ts +16 -0
  136. package/dist/sdk/saved-cards/operations.js +41 -0
  137. package/dist/sdk/saved-cards/operations.js.map +1 -0
  138. package/dist/sdk/saved-cards/use-saved-card-actions.d.ts +26 -0
  139. package/dist/sdk/saved-cards/use-saved-card-actions.js +86 -0
  140. package/dist/sdk/saved-cards/use-saved-card-actions.js.map +1 -0
  141. package/dist/sdk/seo/page-alternates.d.ts +5 -3
  142. package/dist/sdk/seo/page-alternates.js +7 -12
  143. package/dist/sdk/seo/page-alternates.js.map +1 -1
  144. package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
  145. package/dist/sdk/shipping/shipping-operations.js +13 -0
  146. package/dist/sdk/shipping/shipping-operations.js.map +1 -1
  147. package/dist/sdk/state/cart.d.ts +9 -2
  148. package/dist/sdk/state/cart.js +9 -2
  149. package/dist/sdk/state/cart.js.map +1 -1
  150. package/package.json +2 -2
  151. package/src/styles/tailwind.css +1 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode-route-params.js","names":[],"sources":["../../../src/sdk/navigation/decode-route-params.ts"],"sourcesContent":["type RouteParamValue = string | string[] | undefined;\n\nfunction decodeSegment(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\nfunction decodeValue(value: RouteParamValue): RouteParamValue {\n if (value === undefined) return undefined;\n return Array.isArray(value) ? value.map(decodeSegment) : decodeSegment(value);\n}\n\n/**\n * The route params a generated page receives, decoded: Next hands over a dynamic segment\n * percent-encoded, so an id ending in `=` arrives as `%3D`. A value with a malformed sequence is kept\n * as received, so a request can never turn into an exception. A list (catch-all) is decoded segment\n * by segment, and the keys are unchanged.\n */\nexport function decodeRouteParams<T extends Record<string, RouteParamValue>>(params: T): T {\n const decoded: Record<string, RouteParamValue> = {};\n for (const [key, value] of Object.entries(params)) {\n // defineProperty, not assignment: a param named `__proto__` must stay an own key.\n Object.defineProperty(decoded, key, {\n value: decodeValue(value),\n enumerable: true,\n writable: true,\n configurable: true,\n });\n }\n return decoded as T;\n}\n"],"mappings":";AAEA,SAAS,cAAc,SAAyB;CAC9C,IAAI;EACF,OAAO,mBAAmB,OAAO;CACnC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,YAAY,OAAyC;CAC5D,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,IAAI,aAAa,IAAI,cAAc,KAAK;AAC9E;;;;;;;AAQA,SAAgB,kBAA6D,QAAc;CACzF,MAAM,UAA2C,CAAC;CAClD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAE9C,OAAO,eAAe,SAAS,KAAK;EAClC,OAAO,YAAY,KAAK;EACxB,YAAY;EACZ,UAAU;EACV,cAAc;CAChB,CAAC;CAEH,OAAO;AACT"}
@@ -0,0 +1,16 @@
1
+ import { TypedDocumentNode } from "@apollo/client";
2
+ //#region src/sdk/saved-cards/operations.d.ts
3
+ /**
4
+ * Saved-card GraphQL operations: the account's writes against the shopper's saved cards.
5
+ *
6
+ * Authored with the serializable `gql` tag and excluded from the codegen `documents` glob (see
7
+ * `codegen.ts`), like the identity operations: the pluck would otherwise duplicate these
8
+ * hand-written types in `sdk/generated/`.
9
+ */
10
+ /** A single domain error returned by a saved-card write. */
11
+ export type SavedCardUserError = Readonly<{
12
+ code: string;
13
+ message: string;
14
+ }>;
15
+ //#endregion
16
+ //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { gql } from "../graphql-tag.js";
2
+ //#region src/sdk/saved-cards/operations.ts
3
+ /**
4
+ * Selects every field the read exposes: the payload replaces the cached list, so a partial
5
+ * selection would leave the remaining cards holding stale fields.
6
+ */
7
+ const CUSTOMER_SAVED_CARD_REMOVE = gql`
8
+ mutation CustomerSavedCardRemove($id: ID!) {
9
+ customerSavedCardRemove(id: $id) {
10
+ __typename
11
+ customerSavedCards {
12
+ __typename
13
+ id
14
+ cards {
15
+ __typename
16
+ id
17
+ network {
18
+ __typename
19
+ id
20
+ name
21
+ }
22
+ lastDigits
23
+ expiry {
24
+ __typename
25
+ month
26
+ year
27
+ }
28
+ status
29
+ }
30
+ }
31
+ userErrors {
32
+ code
33
+ message
34
+ }
35
+ }
36
+ }
37
+ `;
38
+ //#endregion
39
+ export { CUSTOMER_SAVED_CARD_REMOVE };
40
+
41
+ //# sourceMappingURL=operations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.js","names":[],"sources":["../../../src/sdk/saved-cards/operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * Saved-card GraphQL operations: the account's writes against the shopper's saved cards.\n *\n * Authored with the serializable `gql` tag and excluded from the codegen `documents` glob (see\n * `codegen.ts`), like the identity operations: the pluck would otherwise duplicate these\n * hand-written types in `sdk/generated/`.\n */\n\n/** A single domain error returned by a saved-card write. */\nexport type SavedCardUserError = Readonly<{ code: string; message: string }>;\n\n/** The account's list of saved cards, as `customerSavedCards` reads it. */\nexport type CustomerSavedCardsEntity = Readonly<{\n __typename?: 'CustomerSavedCards';\n id: string;\n cards: ReadonlyArray<{\n __typename?: 'CustomerSavedCard';\n id: string;\n network: { __typename?: 'CardNetwork'; id: string; name: string };\n lastDigits: string;\n expiry: { __typename?: 'CardExpiry'; month: number; year: number } | null;\n status: 'USABLE' | 'EXPIRED' | 'UNUSABLE';\n }>;\n}>;\n\nexport type CustomerSavedCardRemoveMutation = {\n customerSavedCardRemove: {\n __typename?: 'CustomerSavedCardRemovePayload';\n customerSavedCards: CustomerSavedCardsEntity | null;\n userErrors: SavedCardUserError[];\n };\n};\n\nexport type CustomerSavedCardRemoveMutationVariables = {\n id: string;\n};\n\n/**\n * Selects every field the read exposes: the payload replaces the cached list, so a partial\n * selection would leave the remaining cards holding stale fields.\n */\nexport const CUSTOMER_SAVED_CARD_REMOVE: TypedDocumentNode<\n CustomerSavedCardRemoveMutation,\n CustomerSavedCardRemoveMutationVariables\n> = gql`\n mutation CustomerSavedCardRemove($id: ID!) {\n customerSavedCardRemove(id: $id) {\n __typename\n customerSavedCards {\n __typename\n id\n cards {\n __typename\n id\n network {\n __typename\n id\n name\n }\n lastDigits\n expiry {\n __typename\n month\n year\n }\n status\n }\n }\n userErrors {\n code\n message\n }\n }\n }\n`;\n"],"mappings":";;;;;;AA4CA,MAAa,6BAGT,GAAG"}
@@ -0,0 +1,26 @@
1
+ import "../../core/dist/index.js";
2
+ import { SavedCardUserError } from "./operations.js";
3
+ //#region src/sdk/saved-cards/use-saved-card-actions.d.ts
4
+ /**
5
+ * The `savedCard` action namespace — the account's writes against the shopper's saved cards
6
+ * (docs/storefront-core/adr-0010-actions-own-the-write-side.md).
7
+ *
8
+ * Every write is pessimistic: the caller awaits the server, and the returned `CustomerSavedCards`
9
+ * replaces the cached list, so the removed row leaves every subscriber with no refetch. A failure
10
+ * leaves the cache as it was.
11
+ *
12
+ * The removal prompt cannot close while a removal is pending, and the browser client has no request
13
+ * timeout, so `remove` bounds its own request. Past the bound it aborts through the `AbortSignal` in
14
+ * `context.fetchOptions` (honored by `HttpLink`) and resolves `NETWORK_ERROR`. The mutation skips
15
+ * the cache, and `remove` writes only an answer that arrived inside the bound, so a late answer from a
16
+ * link that ignores the abort cannot change the list after the shopper was told it failed.
17
+ */
18
+ export type SavedCardRemovePayload = Readonly<{
19
+ id: string;
20
+ }>;
21
+ export type SavedCardActions = Readonly<{
22
+ /** Removes one saved card. Resolves to the payload's `userErrors` (empty on success). */
23
+ remove: (payload: SavedCardRemovePayload) => Promise<readonly SavedCardUserError[]>;
24
+ }>;
25
+ //#endregion
26
+ //# sourceMappingURL=use-saved-card-actions.d.ts.map
@@ -0,0 +1,86 @@
1
+ "use client";
2
+ import { defineActions } from "../../core/dist/actions/registry.js";
3
+ import "../../core/dist/index.js";
4
+ import { loggableErrorName } from "../../utils/loggable-error-name.js";
5
+ import { CUSTOMER_SAVED_CARD_REMOVE } from "./operations.js";
6
+ import { useApolloClient, useMutation } from "@apollo/client/react";
7
+ import { useCallback, useMemo } from "react";
8
+ import { CombinedGraphQLErrors } from "@apollo/client/errors";
9
+ //#region src/sdk/saved-cards/use-saved-card-actions.ts
10
+ const SAVED_CARD_REMOVE_TIMEOUT_MS = 45e3;
11
+ var SavedCardRemoveTimeoutError = class extends Error {
12
+ constructor() {
13
+ super("Saved card removal timed out.");
14
+ this.name = "SavedCardRemoveTimeoutError";
15
+ }
16
+ };
17
+ const NETWORK_ERROR = {
18
+ code: "NETWORK_ERROR",
19
+ message: "Unable to remove the card. Please try again."
20
+ };
21
+ const UNAUTHORIZED_ERROR = {
22
+ code: "UNAUTHORIZED",
23
+ message: "Your session has expired. Please sign in again."
24
+ };
25
+ /** An expired session needs sign-in, not a retry, so it keeps its own code. */
26
+ function toSavedCardError(error) {
27
+ if (CombinedGraphQLErrors.is(error)) {
28
+ if (error.errors.some(({ extensions }) => extensions?.code === "UNAUTHORIZED")) return UNAUTHORIZED_ERROR;
29
+ }
30
+ return NETWORK_ERROR;
31
+ }
32
+ function useSavedCardActions() {
33
+ const client = useApolloClient();
34
+ const [removeMutation] = useMutation(CUSTOMER_SAVED_CARD_REMOVE);
35
+ const remove = useCallback(async ({ id }) => {
36
+ const controller = new AbortController();
37
+ let timer;
38
+ const timeout = new Promise((_, reject) => {
39
+ timer = setTimeout(() => {
40
+ controller.abort();
41
+ reject(new SavedCardRemoveTimeoutError());
42
+ }, SAVED_CARD_REMOVE_TIMEOUT_MS);
43
+ });
44
+ try {
45
+ const result = await Promise.race([removeMutation({
46
+ variables: { id },
47
+ context: { fetchOptions: { signal: controller.signal } },
48
+ fetchPolicy: "no-cache"
49
+ }), timeout]);
50
+ if (result.data) client.cache.writeQuery({
51
+ id: "ROOT_MUTATION",
52
+ query: CUSTOMER_SAVED_CARD_REMOVE,
53
+ variables: { id },
54
+ data: result.data
55
+ });
56
+ const payload = result.data?.customerSavedCardRemove;
57
+ return payload ? payload.userErrors : [NETWORK_ERROR];
58
+ } catch (error) {
59
+ console.error("[savedCard.remove] customerSavedCardRemove failed", loggableErrorName(error));
60
+ return [toSavedCardError(error)];
61
+ } finally {
62
+ clearTimeout(timer);
63
+ }
64
+ }, [client, removeMutation]);
65
+ return useMemo(() => ({ remove }), [remove]);
66
+ }
67
+ defineActions({
68
+ namespace: "savedCard",
69
+ description: "Mutation-backed writes against the signed-in shopper's saved cards.",
70
+ provider: {
71
+ name: "SavedCardActionProvider",
72
+ importPath: "@vtex/faststore/internal/client"
73
+ },
74
+ methods: { remove: {
75
+ description: "Remove one saved card (async strategy). The row stays until the server answers.",
76
+ payload: [{
77
+ name: "id",
78
+ type: "string"
79
+ }]
80
+ } },
81
+ use: useSavedCardActions
82
+ });
83
+ //#endregion
84
+ export { SAVED_CARD_REMOVE_TIMEOUT_MS, useSavedCardActions };
85
+
86
+ //# sourceMappingURL=use-saved-card-actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-saved-card-actions.js","names":[],"sources":["../../../src/sdk/saved-cards/use-saved-card-actions.ts"],"sourcesContent":["'use client';\n\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { useApolloClient, useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { CUSTOMER_SAVED_CARD_REMOVE, type SavedCardUserError } from './operations.js';\n\n/**\n * The `savedCard` action namespace — the account's writes against the shopper's saved cards\n * (docs/storefront-core/adr-0010-actions-own-the-write-side.md).\n *\n * Every write is pessimistic: the caller awaits the server, and the returned `CustomerSavedCards`\n * replaces the cached list, so the removed row leaves every subscriber with no refetch. A failure\n * leaves the cache as it was.\n *\n * The removal prompt cannot close while a removal is pending, and the browser client has no request\n * timeout, so `remove` bounds its own request. Past the bound it aborts through the `AbortSignal` in\n * `context.fetchOptions` (honored by `HttpLink`) and resolves `NETWORK_ERROR`. The mutation skips\n * the cache, and `remove` writes only an answer that arrived inside the bound, so a late answer from a\n * link that ignores the abort cannot change the list after the shopper was told it failed.\n */\n\nexport type SavedCardRemovePayload = Readonly<{ id: string }>;\n\nexport type SavedCardActions = Readonly<{\n /** Removes one saved card. Resolves to the payload's `userErrors` (empty on success). */\n remove: (payload: SavedCardRemovePayload) => Promise<readonly SavedCardUserError[]>;\n}>;\n\n// Above the resolver's own upstream budget (10 s per call, up to four in series), so the bound fires\n// only on a genuine hang.\nexport const SAVED_CARD_REMOVE_TIMEOUT_MS = 45_000;\n\nclass SavedCardRemoveTimeoutError extends Error {\n constructor() {\n super('Saved card removal timed out.');\n this.name = 'SavedCardRemoveTimeoutError';\n }\n}\n\nconst NETWORK_ERROR: SavedCardUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to remove the card. Please try again.',\n};\n\nconst UNAUTHORIZED_ERROR: SavedCardUserError = {\n code: 'UNAUTHORIZED',\n message: 'Your session has expired. Please sign in again.',\n};\n\n/** An expired session needs sign-in, not a retry, so it keeps its own code. */\nfunction toSavedCardError(error: unknown): SavedCardUserError {\n if (CombinedGraphQLErrors.is(error)) {\n const unauthorized = error.errors.some(({ extensions }) => extensions?.code === 'UNAUTHORIZED');\n if (unauthorized) return UNAUTHORIZED_ERROR;\n }\n return NETWORK_ERROR;\n}\n\nexport function useSavedCardActions(): SavedCardActions {\n const client = useApolloClient();\n const [removeMutation] = useMutation(CUSTOMER_SAVED_CARD_REMOVE);\n\n const remove = useCallback<SavedCardActions['remove']>(\n async ({ id }) => {\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n reject(new SavedCardRemoveTimeoutError());\n }, SAVED_CARD_REMOVE_TIMEOUT_MS);\n });\n\n try {\n const result = await Promise.race([\n removeMutation({\n variables: { id },\n context: { fetchOptions: { signal: controller.signal } },\n fetchPolicy: 'no-cache',\n }),\n timeout,\n ]);\n if (result.data) {\n client.cache.writeQuery({\n id: 'ROOT_MUTATION',\n query: CUSTOMER_SAVED_CARD_REMOVE,\n variables: { id },\n data: result.data,\n });\n }\n // A missing payload removed nothing, so it must never read as success.\n const payload = result.data?.customerSavedCardRemove;\n return payload ? payload.userErrors : [NETWORK_ERROR];\n } catch (error) {\n console.error('[savedCard.remove] customerSavedCardRemove failed', loggableErrorName(error));\n return [toSavedCardError(error)];\n } finally {\n clearTimeout(timer);\n }\n },\n [client, removeMutation],\n );\n\n return useMemo(() => ({ remove }), [remove]);\n}\n\n/**\n * Declares the `savedCard` action namespace. The catalog builder reads this call to emit\n * `actions.json`; at runtime `SavedCardActionProvider` calls `useSavedCardActions` directly.\n */\nexport const savedCardActions = defineActions({\n namespace: 'savedCard',\n description: \"Mutation-backed writes against the signed-in shopper's saved cards.\",\n provider: { name: 'SavedCardActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n remove: {\n description: 'Remove one saved card (async strategy). The row stays until the server answers.',\n payload: [{ name: 'id', type: 'string' }],\n },\n },\n use: useSavedCardActions,\n});\n"],"mappings":";;;;;;;;;AAiCA,MAAa,+BAA+B;AAE5C,IAAM,8BAAN,cAA0C,MAAM;CAC9C,cAAc;EACZ,MAAM,+BAA+B;EACrC,KAAK,OAAO;CACd;AACF;AAEA,MAAM,gBAAoC;CACxC,MAAM;CACN,SAAS;AACX;AAEA,MAAM,qBAAyC;CAC7C,MAAM;CACN,SAAS;AACX;;AAGA,SAAS,iBAAiB,OAAoC;CAC5D,IAAI,sBAAsB,GAAG,KAAK,GACX;MAAA,MAAM,OAAO,MAAM,EAAE,iBAAiB,YAAY,SAAS,cACjE,GAAG,OAAO;CAAA;CAE3B,OAAO;AACT;AAEA,SAAgB,sBAAwC;CACtD,MAAM,SAAS,gBAAgB;CAC/B,MAAM,CAAC,kBAAkB,YAAY,0BAA0B;CAE/D,MAAM,SAAS,YACb,OAAO,EAAE,SAAS;EAChB,MAAM,aAAa,IAAI,gBAAgB;EACvC,IAAI;EACJ,MAAM,UAAU,IAAI,SAAgB,GAAG,WAAW;GAChD,QAAQ,iBAAiB;IACvB,WAAW,MAAM;IACjB,OAAO,IAAI,4BAA4B,CAAC;GAC1C,GAAG,4BAA4B;EACjC,CAAC;EAED,IAAI;GACF,MAAM,SAAS,MAAM,QAAQ,KAAK,CAChC,eAAe;IACb,WAAW,EAAE,GAAG;IAChB,SAAS,EAAE,cAAc,EAAE,QAAQ,WAAW,OAAO,EAAE;IACvD,aAAa;GACf,CAAC,GACD,OACF,CAAC;GACD,IAAI,OAAO,MACT,OAAO,MAAM,WAAW;IACtB,IAAI;IACJ,OAAO;IACP,WAAW,EAAE,GAAG;IAChB,MAAM,OAAO;GACf,CAAC;GAGH,MAAM,UAAU,OAAO,MAAM;GAC7B,OAAO,UAAU,QAAQ,aAAa,CAAC,aAAa;EACtD,SAAS,OAAO;GACd,QAAQ,MAAM,qDAAqD,kBAAkB,KAAK,CAAC;GAC3F,OAAO,CAAC,iBAAiB,KAAK,CAAC;EACjC,UAAU;GACR,aAAa,KAAK;EACpB;CACF,GACA,CAAC,QAAQ,cAAc,CACzB;CAEA,OAAO,eAAe,EAAE,OAAO,IAAI,CAAC,MAAM,CAAC;AAC7C;AAMgC,cAAc;CAC5C,WAAW;CACX,aAAa;CACb,UAAU;EAAE,MAAM;EAA2B,YAAY;CAAkC;CAC3F,SAAS,EACP,QAAQ;EACN,aAAa;EACb,SAAS,CAAC;GAAE,MAAM;GAAM,MAAM;EAAS,CAAC;CAC1C,EACF;CACA,KAAK;AACP,CAAC"}
@@ -36,9 +36,11 @@ export declare const CATALOG_ROUTE_ALTERNATES_QUERY: import("@graphql-typed-docu
36
36
  */
37
37
  export declare function buildPageAlternates({ bindingId, defaultBindingId, bindings, selfPath, clusterPaths, buildUrl }: BuildPageAlternatesOptions): PageAlternates;
38
38
  /**
39
- * The decoded logical path a route pattern names under these params:
40
- * `('/[slug]/p', { slug: 'caf%C3%A9' })` -> `'/café/p'`. Catch-alls join with `/`; empty -> `'/'`.
41
- * Empty, `.` and `..` values are dropped.
39
+ * The logical path a route pattern names under these params, which arrive decoded
40
+ * (`decodeRouteParams`): `('/[slug]/p', { slug: 'café' })` -> `'/café/p'`. Catch-alls join with `/`;
41
+ * empty -> `'/'`.
42
+ * Empty, `.` and `..` values are dropped. It never decodes: a caller must decode first, or an
43
+ * encoded dot segment (`%2E%2E`) stays an inert literal segment.
42
44
  */
43
45
  export declare function routeLogicalPath(route: string, params: RouteParams): string;
44
46
  /**
@@ -57,27 +57,22 @@ function buildPageAlternates({ bindingId, defaultBindingId, bindings, selfPath,
57
57
  languages
58
58
  };
59
59
  }
60
- function decodeSegment(segment) {
61
- try {
62
- return decodeURIComponent(segment);
63
- } catch {
64
- return segment;
65
- }
66
- }
67
60
  /** `.` and `..` would make the URL a crawler resolves differ from the page that states it. */
68
61
  function isPathSegment(segment) {
69
62
  return segment !== "" && segment !== "." && segment !== "..";
70
63
  }
71
64
  /**
72
- * The decoded logical path a route pattern names under these params:
73
- * `('/[slug]/p', { slug: 'caf%C3%A9' })` -> `'/café/p'`. Catch-alls join with `/`; empty -> `'/'`.
74
- * Empty, `.` and `..` values are dropped.
65
+ * The logical path a route pattern names under these params, which arrive decoded
66
+ * (`decodeRouteParams`): `('/[slug]/p', { slug: 'café' })` -> `'/café/p'`. Catch-alls join with `/`;
67
+ * empty -> `'/'`.
68
+ * Empty, `.` and `..` values are dropped. It never decodes: a caller must decode first, or an
69
+ * encoded dot segment (`%2E%2E`) stays an inert literal segment.
75
70
  */
76
71
  function routeLogicalPath(route, params) {
77
72
  return `/${route.split("/").filter(Boolean).flatMap((segment) => {
78
73
  const dynamic = /^\[{1,2}(?:\.\.\.)?([^\]]+)\]{1,2}$/.exec(segment);
79
74
  if (!dynamic) return [segment];
80
- return [params[dynamic[1]] ?? []].flat().map(decodeSegment);
75
+ return [params[dynamic[1]] ?? []].flat();
81
76
  }).filter(isPathSegment).join("/")}`;
82
77
  }
83
78
  function readVariants(data) {
@@ -127,6 +122,6 @@ async function catalogClusterPaths(bindings, load) {
127
122
  }
128
123
  }
129
124
  //#endregion
130
- export { CATALOG_ROUTE_ALTERNATES_QUERY, buildPageAlternates, catalogClusterPaths, routeLogicalPath };
125
+ export { CATALOG_ROUTE_ALTERNATES_QUERY, buildPageAlternates, catalogClusterPaths, encodePath, routeLogicalPath };
131
126
 
132
127
  //# sourceMappingURL=page-alternates.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"page-alternates.js","names":[],"sources":["../../../src/sdk/seo/page-alternates.ts"],"sourcesContent":["import { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { graphql } from '../generated/gql.js';\nimport type { CatalogRouteAlternatesQuery } from '../generated/graphql.js';\n\nexport type AlternateBinding = { bindingId: string; locale: string };\n\nexport type RouteVariant = NonNullable<CatalogRouteAlternatesQuery['catalogRoute']>['variants'][number];\n\n/** Structurally assignable to Next.js `Metadata['alternates']`. */\nexport type PageAlternates = {\n canonical: string;\n /** BCP 47 tag or `x-default` -> absolute URL. */\n languages?: Record<string, string>;\n};\n\nexport type RouteParams = Readonly<Record<string, string | string[] | undefined>>;\n\nexport type BuildPageAlternatesOptions = {\n bindingId: string;\n defaultBindingId: string;\n /** Every declared binding, in authoring order. */\n bindings: readonly AlternateBinding[];\n /** Decoded logical path of the request. */\n selfPath: string;\n /** Binding id -> decoded logical path, for the cluster members only. `null` means no cluster source. */\n clusterPaths: Readonly<Record<string, string>> | null;\n buildUrl: (bindingId: string, logicalPath: string) => string;\n};\n\n/**\n * The only GraphQL text the alternates feature owns. `catalogClusterPaths` reads its result, and the\n * package tests validate it against the Storefront API schema.\n */\nexport const CATALOG_ROUTE_ALTERNATES_QUERY = graphql(`\n query CatalogRouteAlternates($path: String!) {\n catalogRoute(input: { path: $path }) {\n variants {\n locale\n path\n }\n }\n }\n`);\n\n/** Encodes each segment and keeps `/` literal, so a decoded path is encoded exactly once. */\nfunction encodePath(path: string): string {\n return path.split('/').map(encodeURIComponent).join('/');\n}\n\n/**\n * One binding per locale: `hreflang` maps a tag to a single URL. The default binding represents a\n * locale it declares; otherwise the first binding declaring it does.\n */\nfunction representatives(bindings: readonly AlternateBinding[], defaultBindingId: string): AlternateBinding[] {\n const byLocale = new Map<string, AlternateBinding>();\n for (const binding of bindings) {\n const key = binding.locale.toLowerCase();\n const current = byLocale.get(key);\n if (!current || binding.bindingId === defaultBindingId) byLocale.set(key, binding);\n }\n return bindings.filter((binding) => byLocale.get(binding.locale.toLowerCase()) === binding);\n}\n\n/**\n * The canonical URL plus, when a reciprocal cluster exists, `languages`. A cluster needs two members\n * and must include the rendered binding: search engines ignore a set that does not reference itself.\n */\nexport function buildPageAlternates({\n bindingId,\n defaultBindingId,\n bindings,\n selfPath,\n clusterPaths,\n buildUrl,\n}: BuildPageAlternatesOptions): PageAlternates {\n const url = (id: string, path: string) => buildUrl(id, encodePath(path));\n const pathOf = (id: string) => (clusterPaths && Object.hasOwn(clusterPaths, id) ? clusterPaths[id] : undefined);\n const canonical = url(bindingId, pathOf(bindingId) ?? selfPath);\n\n const members = representatives(bindings, defaultBindingId).flatMap((binding) => {\n const path = pathOf(binding.bindingId);\n return path === undefined ? [] : [{ ...binding, href: url(binding.bindingId, path) }];\n });\n if (members.length < 2 || !members.some((member) => member.bindingId === bindingId)) return { canonical };\n\n const languages: Record<string, string> = {};\n for (const member of members) languages[member.locale] = member.href;\n const defaultMember = members.find((member) => member.bindingId === defaultBindingId);\n if (defaultMember) languages['x-default'] = defaultMember.href;\n return { canonical, languages };\n}\n\nfunction decodeSegment(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\n/** `.` and `..` would make the URL a crawler resolves differ from the page that states it. */\nfunction isPathSegment(segment: string): boolean {\n return segment !== '' && segment !== '.' && segment !== '..';\n}\n\n/**\n * The decoded logical path a route pattern names under these params:\n * `('/[slug]/p', { slug: 'caf%C3%A9' })` -> `'/café/p'`. Catch-alls join with `/`; empty -> `'/'`.\n * Empty, `.` and `..` values are dropped.\n */\nexport function routeLogicalPath(route: string, params: RouteParams): string {\n const segments = route\n .split('/')\n .filter(Boolean)\n .flatMap((segment) => {\n const dynamic = /^\\[{1,2}(?:\\.\\.\\.)?([^\\]]+)\\]{1,2}$/.exec(segment);\n if (!dynamic) return [segment];\n return [params[dynamic[1] as string] ?? []].flat().map(decodeSegment);\n })\n .filter(isPathSegment);\n return `/${segments.join('/')}`;\n}\n\nfunction readVariants(data: unknown): RouteVariant[] {\n const variants = (data as { catalogRoute?: { variants?: unknown } | null } | null)?.catalogRoute?.variants;\n if (!Array.isArray(variants)) return [];\n return variants.filter(\n (variant): variant is RouteVariant => typeof variant?.locale === 'string' && typeof variant?.path === 'string',\n );\n}\n\n/** Binding id -> confirmed variant path, matching BCP 47 tags case-insensitively and nothing looser. */\nfunction variantPathsByBinding(\n bindings: readonly AlternateBinding[],\n variants: readonly RouteVariant[],\n): Record<string, string> {\n const paths: Record<string, string> = {};\n for (const binding of bindings) {\n const locale = binding.locale.toLowerCase();\n const variant = variants.find((candidate) => candidate.locale.toLowerCase() === locale);\n if (variant) paths[binding.bindingId] = variant.path;\n }\n return paths;\n}\n\n/**\n * Why a lookup degraded, safe to log: the GraphQL error code, else the allow-listed error name. `null`\n * for `BAD_USER_INPUT`, which a path `catalogRoute` cannot parse (a slug with a dot) always answers.\n */\nfunction degradedReason(error: unknown): string | null {\n if (!CombinedGraphQLErrors.is(error)) return loggableErrorName(error);\n const codes = error.errors.map(({ extensions }) => extensions?.code);\n if (codes.length > 0 && codes.every((candidate) => candidate === 'BAD_USER_INPUT')) return null;\n const code = codes.find(\n (candidate): candidate is string => typeof candidate === 'string' && candidate !== 'BAD_USER_INPUT',\n );\n return code ?? 'GRAPHQL_ERROR';\n}\n\nfunction warnDegraded(error: unknown): null {\n const reason = degradedReason(error);\n if (reason) console.warn('[seo.catalogClusterPaths] catalogRoute lookup degraded to no cluster', reason);\n return null;\n}\n\n/**\n * Binding id -> variant path for the bindings whose locale a confirmed variant matches. Never\n * rejects: a thrown loader, an `error`, a missing or `null` `catalogRoute`, or no matching variant\n * all resolve to `null`, so a catalog lookup can never fail the page it describes. A thrown loader\n * or an `error` is logged by code or error name only, never with the path or the upstream message.\n */\nexport async function catalogClusterPaths(\n bindings: readonly AlternateBinding[],\n load: () => Promise<{ data: unknown; error?: unknown }>,\n): Promise<Record<string, string> | null> {\n try {\n const { data, error } = await load();\n if (error) return warnDegraded(error);\n const paths = variantPathsByBinding(bindings, readVariants(data));\n return Object.keys(paths).length > 0 ? paths : null;\n } catch (error) {\n return warnDegraded(error);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"page-alternates.js","names":[],"sources":["../../../src/sdk/seo/page-alternates.ts"],"sourcesContent":["import { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { graphql } from '../generated/gql.js';\nimport type { CatalogRouteAlternatesQuery } from '../generated/graphql.js';\n\nexport type AlternateBinding = { bindingId: string; locale: string };\n\nexport type RouteVariant = NonNullable<CatalogRouteAlternatesQuery['catalogRoute']>['variants'][number];\n\n/** Structurally assignable to Next.js `Metadata['alternates']`. */\nexport type PageAlternates = {\n canonical: string;\n /** BCP 47 tag or `x-default` -> absolute URL. */\n languages?: Record<string, string>;\n};\n\nexport type RouteParams = Readonly<Record<string, string | string[] | undefined>>;\n\nexport type BuildPageAlternatesOptions = {\n bindingId: string;\n defaultBindingId: string;\n /** Every declared binding, in authoring order. */\n bindings: readonly AlternateBinding[];\n /** Decoded logical path of the request. */\n selfPath: string;\n /** Binding id -> decoded logical path, for the cluster members only. `null` means no cluster source. */\n clusterPaths: Readonly<Record<string, string>> | null;\n buildUrl: (bindingId: string, logicalPath: string) => string;\n};\n\n/**\n * The only GraphQL text the alternates feature owns. `catalogClusterPaths` reads its result, and the\n * package tests validate it against the Storefront API schema.\n */\nexport const CATALOG_ROUTE_ALTERNATES_QUERY = graphql(`\n query CatalogRouteAlternates($path: String!) {\n catalogRoute(input: { path: $path }) {\n variants {\n locale\n path\n }\n }\n }\n`);\n\n/** Encodes each segment and keeps `/` literal, so a decoded path is encoded exactly once. */\nexport function encodePath(path: string): string {\n return path.split('/').map(encodeURIComponent).join('/');\n}\n\n/**\n * One binding per locale: `hreflang` maps a tag to a single URL. The default binding represents a\n * locale it declares; otherwise the first binding declaring it does.\n */\nfunction representatives(bindings: readonly AlternateBinding[], defaultBindingId: string): AlternateBinding[] {\n const byLocale = new Map<string, AlternateBinding>();\n for (const binding of bindings) {\n const key = binding.locale.toLowerCase();\n const current = byLocale.get(key);\n if (!current || binding.bindingId === defaultBindingId) byLocale.set(key, binding);\n }\n return bindings.filter((binding) => byLocale.get(binding.locale.toLowerCase()) === binding);\n}\n\n/**\n * The canonical URL plus, when a reciprocal cluster exists, `languages`. A cluster needs two members\n * and must include the rendered binding: search engines ignore a set that does not reference itself.\n */\nexport function buildPageAlternates({\n bindingId,\n defaultBindingId,\n bindings,\n selfPath,\n clusterPaths,\n buildUrl,\n}: BuildPageAlternatesOptions): PageAlternates {\n const url = (id: string, path: string) => buildUrl(id, encodePath(path));\n const pathOf = (id: string) => (clusterPaths && Object.hasOwn(clusterPaths, id) ? clusterPaths[id] : undefined);\n const canonical = url(bindingId, pathOf(bindingId) ?? selfPath);\n\n const members = representatives(bindings, defaultBindingId).flatMap((binding) => {\n const path = pathOf(binding.bindingId);\n return path === undefined ? [] : [{ ...binding, href: url(binding.bindingId, path) }];\n });\n if (members.length < 2 || !members.some((member) => member.bindingId === bindingId)) return { canonical };\n\n const languages: Record<string, string> = {};\n for (const member of members) languages[member.locale] = member.href;\n const defaultMember = members.find((member) => member.bindingId === defaultBindingId);\n if (defaultMember) languages['x-default'] = defaultMember.href;\n return { canonical, languages };\n}\n\n/** `.` and `..` would make the URL a crawler resolves differ from the page that states it. */\nfunction isPathSegment(segment: string): boolean {\n return segment !== '' && segment !== '.' && segment !== '..';\n}\n\n/**\n * The logical path a route pattern names under these params, which arrive decoded\n * (`decodeRouteParams`): `('/[slug]/p', { slug: 'café' })` -> `'/café/p'`. Catch-alls join with `/`;\n * empty -> `'/'`.\n * Empty, `.` and `..` values are dropped. It never decodes: a caller must decode first, or an\n * encoded dot segment (`%2E%2E`) stays an inert literal segment.\n */\nexport function routeLogicalPath(route: string, params: RouteParams): string {\n const segments = route\n .split('/')\n .filter(Boolean)\n .flatMap((segment) => {\n const dynamic = /^\\[{1,2}(?:\\.\\.\\.)?([^\\]]+)\\]{1,2}$/.exec(segment);\n if (!dynamic) return [segment];\n return [params[dynamic[1] as string] ?? []].flat();\n })\n .filter(isPathSegment);\n return `/${segments.join('/')}`;\n}\n\nfunction readVariants(data: unknown): RouteVariant[] {\n const variants = (data as { catalogRoute?: { variants?: unknown } | null } | null)?.catalogRoute?.variants;\n if (!Array.isArray(variants)) return [];\n return variants.filter(\n (variant): variant is RouteVariant => typeof variant?.locale === 'string' && typeof variant?.path === 'string',\n );\n}\n\n/** Binding id -> confirmed variant path, matching BCP 47 tags case-insensitively and nothing looser. */\nfunction variantPathsByBinding(\n bindings: readonly AlternateBinding[],\n variants: readonly RouteVariant[],\n): Record<string, string> {\n const paths: Record<string, string> = {};\n for (const binding of bindings) {\n const locale = binding.locale.toLowerCase();\n const variant = variants.find((candidate) => candidate.locale.toLowerCase() === locale);\n if (variant) paths[binding.bindingId] = variant.path;\n }\n return paths;\n}\n\n/**\n * Why a lookup degraded, safe to log: the GraphQL error code, else the allow-listed error name. `null`\n * for `BAD_USER_INPUT`, which a path `catalogRoute` cannot parse (a slug with a dot) always answers.\n */\nfunction degradedReason(error: unknown): string | null {\n if (!CombinedGraphQLErrors.is(error)) return loggableErrorName(error);\n const codes = error.errors.map(({ extensions }) => extensions?.code);\n if (codes.length > 0 && codes.every((candidate) => candidate === 'BAD_USER_INPUT')) return null;\n const code = codes.find(\n (candidate): candidate is string => typeof candidate === 'string' && candidate !== 'BAD_USER_INPUT',\n );\n return code ?? 'GRAPHQL_ERROR';\n}\n\nfunction warnDegraded(error: unknown): null {\n const reason = degradedReason(error);\n if (reason) console.warn('[seo.catalogClusterPaths] catalogRoute lookup degraded to no cluster', reason);\n return null;\n}\n\n/**\n * Binding id -> variant path for the bindings whose locale a confirmed variant matches. Never\n * rejects: a thrown loader, an `error`, a missing or `null` `catalogRoute`, or no matching variant\n * all resolve to `null`, so a catalog lookup can never fail the page it describes. A thrown loader\n * or an `error` is logged by code or error name only, never with the path or the upstream message.\n */\nexport async function catalogClusterPaths(\n bindings: readonly AlternateBinding[],\n load: () => Promise<{ data: unknown; error?: unknown }>,\n): Promise<Record<string, string> | null> {\n try {\n const { data, error } = await load();\n if (error) return warnDegraded(error);\n const paths = variantPathsByBinding(bindings, readVariants(data));\n return Object.keys(paths).length > 0 ? paths : null;\n } catch (error) {\n return warnDegraded(error);\n }\n}\n"],"mappings":";;;;;;;;AAkCA,MAAa,iCAAiC,QAAQ;;;;;;;;;CASrD;;AAGD,SAAgB,WAAW,MAAsB;CAC/C,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,kBAAkB,CAAC,CAAC,KAAK,GAAG;AACzD;;;;;AAMA,SAAS,gBAAgB,UAAuC,kBAA8C;CAC5G,MAAM,2BAAW,IAAI,IAA8B;CACnD,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,MAAM,QAAQ,OAAO,YAAY;EAEvC,IAAI,CADY,SAAS,IAAI,GAClB,KAAK,QAAQ,cAAc,kBAAkB,SAAS,IAAI,KAAK,OAAO;CACnF;CACA,OAAO,SAAS,QAAQ,YAAY,SAAS,IAAI,QAAQ,OAAO,YAAY,CAAC,MAAM,OAAO;AAC5F;;;;;AAMA,SAAgB,oBAAoB,EAClC,WACA,kBACA,UACA,UACA,cACA,YAC6C;CAC7C,MAAM,OAAO,IAAY,SAAiB,SAAS,IAAI,WAAW,IAAI,CAAC;CACvE,MAAM,UAAU,OAAgB,gBAAgB,OAAO,OAAO,cAAc,EAAE,IAAI,aAAa,MAAM,KAAA;CACrG,MAAM,YAAY,IAAI,WAAW,OAAO,SAAS,KAAK,QAAQ;CAE9D,MAAM,UAAU,gBAAgB,UAAU,gBAAgB,CAAC,CAAC,SAAS,YAAY;EAC/E,MAAM,OAAO,OAAO,QAAQ,SAAS;EACrC,OAAO,SAAS,KAAA,IAAY,CAAC,IAAI,CAAC;GAAE,GAAG;GAAS,MAAM,IAAI,QAAQ,WAAW,IAAI;EAAE,CAAC;CACtF,CAAC;CACD,IAAI,QAAQ,SAAS,KAAK,CAAC,QAAQ,MAAM,WAAW,OAAO,cAAc,SAAS,GAAG,OAAO,EAAE,UAAU;CAExG,MAAM,YAAoC,CAAC;CAC3C,KAAK,MAAM,UAAU,SAAS,UAAU,OAAO,UAAU,OAAO;CAChE,MAAM,gBAAgB,QAAQ,MAAM,WAAW,OAAO,cAAc,gBAAgB;CACpF,IAAI,eAAe,UAAU,eAAe,cAAc;CAC1D,OAAO;EAAE;EAAW;CAAU;AAChC;;AAGA,SAAS,cAAc,SAA0B;CAC/C,OAAO,YAAY,MAAM,YAAY,OAAO,YAAY;AAC1D;;;;;;;;AASA,SAAgB,iBAAiB,OAAe,QAA6B;CAU3E,OAAO,IATU,MACd,MAAM,GAAG,CAAC,CACV,OAAO,OAAO,CAAC,CACf,SAAS,YAAY;EACpB,MAAM,UAAU,sCAAsC,KAAK,OAAO;EAClE,IAAI,CAAC,SAAS,OAAO,CAAC,OAAO;EAC7B,OAAO,CAAC,OAAO,QAAQ,OAAiB,CAAC,CAAC,CAAC,CAAC,KAAK;CACnD,CAAC,CAAC,CACD,OAAO,aACQ,CAAC,CAAC,KAAK,GAAG;AAC9B;AAEA,SAAS,aAAa,MAA+B;CACnD,MAAM,WAAY,MAAkE,cAAc;CAClG,IAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG,OAAO,CAAC;CACtC,OAAO,SAAS,QACb,YAAqC,OAAO,SAAS,WAAW,YAAY,OAAO,SAAS,SAAS,QACxG;AACF;;AAGA,SAAS,sBACP,UACA,UACwB;CACxB,MAAM,QAAgC,CAAC;CACvC,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,SAAS,QAAQ,OAAO,YAAY;EAC1C,MAAM,UAAU,SAAS,MAAM,cAAc,UAAU,OAAO,YAAY,MAAM,MAAM;EACtF,IAAI,SAAS,MAAM,QAAQ,aAAa,QAAQ;CAClD;CACA,OAAO;AACT;;;;;AAMA,SAAS,eAAe,OAA+B;CACrD,IAAI,CAAC,sBAAsB,GAAG,KAAK,GAAG,OAAO,kBAAkB,KAAK;CACpE,MAAM,QAAQ,MAAM,OAAO,KAAK,EAAE,iBAAiB,YAAY,IAAI;CACnE,IAAI,MAAM,SAAS,KAAK,MAAM,OAAO,cAAc,cAAc,gBAAgB,GAAG,OAAO;CAI3F,OAHa,MAAM,MAChB,cAAmC,OAAO,cAAc,YAAY,cAAc,gBAE3E,KAAK;AACjB;AAEA,SAAS,aAAa,OAAsB;CAC1C,MAAM,SAAS,eAAe,KAAK;CACnC,IAAI,QAAQ,QAAQ,KAAK,wEAAwE,MAAM;CACvG,OAAO;AACT;;;;;;;AAQA,eAAsB,oBACpB,UACA,MACwC;CACxC,IAAI;EACF,MAAM,EAAE,MAAM,UAAU,MAAM,KAAK;EACnC,IAAI,OAAO,OAAO,aAAa,KAAK;EACpC,MAAM,QAAQ,sBAAsB,UAAU,aAAa,IAAI,CAAC;EAChE,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,QAAQ;CACjD,SAAS,OAAO;EACd,OAAO,aAAa,KAAK;CAC3B;AACF"}
@@ -24,7 +24,7 @@ export type ShippingAddressUpdateErrorEntity = Readonly<{
24
24
  }>;
25
25
  export type ShippingAddressInput = Readonly<{
26
26
  savedAddressId?: string;
27
- recipientName: string;
27
+ recipientName?: string;
28
28
  postalCode: string;
29
29
  country: string;
30
30
  street?: string;
@@ -62,6 +62,19 @@ const SHIPPING_ADDRESS_UPDATE = gql`
62
62
  }
63
63
  cart {
64
64
  ...CartFragment
65
+ freeShippingProgress {
66
+ threshold {
67
+ __typename
68
+ amount
69
+ currencyCode
70
+ }
71
+ remaining {
72
+ __typename
73
+ amount
74
+ currencyCode
75
+ }
76
+ qualified
77
+ }
65
78
  }
66
79
  userErrors {
67
80
  code
@@ -1 +1 @@
1
- {"version":3,"file":"shipping-operations.js","names":[],"sources":["../../../src/sdk/shipping/shipping-operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { type CartEntity, CartFragment } from '../cart/operations.js';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * Shipping address mutation documents (docs/storefront-core/reference-free-particle-model.md § The cart write contract).\n *\n * Authored with the hand `gql` tag — the codegen `graphql()` factory is reserved for the reactive\n * read path (`operations.ts`) — and explicitly excluded from the codegen `documents` glob (see\n * `codegen.ts`): the `gql` tag alone is still plucked by `graphql-codegen`, which would duplicate\n * these hand-written types in `sdk/generated/`. The mutation stays hand-typed as a\n * `TypedDocumentNode` (Decision 3 in the spec). The payload projects the full refreshed `shipping`\n * and `cart` state, so the response write into the cache *is* the post-update refresh: `Shipping`'s\n * reactive read flips with no refetch and no imperative cache write.\n *\n * Failures follow ADR-00007's two-path contract: domain outcomes arrive as payload `userErrors`\n * (`ShippingAddressUpdateError`); transport and top-level GraphQL failures are infrastructure,\n * mapped by the handlers in `use-shipping-actions.ts` to the one client-synthesized code,\n * `NETWORK_USER_ERROR`.\n */\n\n/** A typed address-update failure; NETWORK_USER_ERROR is the one client-synthesized code. */\nexport type ShippingAddressUpdateErrorEntity = Readonly<{ code: string; message: string }>;\n\n/**\n * The one client-synthesized error code, carried by the userError that `setAddress` *resolves*\n * (never throws) when the mutation fails at the transport/top-level-GraphQL layer. Consumers use\n * it to route the resolved synthetic failure to the generic upstream error state (Decision 5),\n * distinct from the domain `userErrors` codes that render inline.\n */\nexport const SHIPPING_NETWORK_ERROR_CODE = 'NETWORK_ERROR';\n\nexport type ShippingAddressInput = Readonly<{\n savedAddressId?: string;\n recipientName: string;\n postalCode: string;\n country: string;\n street?: string;\n number?: string;\n complement?: string;\n neighborhood?: string;\n city?: string;\n state?: string;\n}>;\n\n/** The full shippingAddressUpdate mutation payload. */\nexport type ShippingAddressUpdatePayloadEntity = Readonly<{\n __typename: 'ShippingAddressUpdatePayload';\n shipping: {\n __typename: 'Shipping';\n id: string;\n address: {\n __typename: 'Address';\n id?: string | null;\n recipientName?: string | null;\n postalCode?: string | null;\n country: string;\n street?: string | null;\n number?: string | null;\n complement?: string | null;\n neighborhood?: string | null;\n city?: string | null;\n state?: string | null;\n } | null;\n savedAddresses: ReadonlyArray<{\n __typename: 'Address';\n id?: string | null;\n recipientName?: string | null;\n postalCode?: string | null;\n country: string;\n street?: string | null;\n number?: string | null;\n complement?: string | null;\n neighborhood?: string | null;\n city?: string | null;\n state?: string | null;\n }>;\n options: ReadonlyArray<{\n __typename: 'ShippingOption';\n id: string;\n name: string;\n channel: string;\n shippingEstimate: { __typename: 'ShippingEstimate'; value: number; unit: string };\n price: { __typename: 'Money'; amount: number; currencyCode: string };\n listPrice: { __typename: 'Money'; amount: number; currencyCode: string };\n }>;\n selectedOptionId?: string | null;\n } | null;\n cart: CartEntity;\n userErrors: readonly ShippingAddressUpdateErrorEntity[];\n}>;\n\ntype ShippingAddressUpdateData = Readonly<{ shippingAddressUpdate: ShippingAddressUpdatePayloadEntity }>;\n\nexport const SHIPPING_ADDRESS_UPDATE: TypedDocumentNode<ShippingAddressUpdateData, { input: ShippingAddressInput }> =\n gql`\n mutation ShippingAddressUpdate($input: ShippingAddressUpdateInput!) {\n shippingAddressUpdate(input: $input) {\n shipping {\n __typename\n id\n address {\n __typename\n id\n recipientName\n postalCode\n country\n street\n number\n complement\n neighborhood\n city\n state\n }\n savedAddresses {\n __typename\n id\n recipientName\n postalCode\n country\n street\n number\n complement\n neighborhood\n city\n state\n }\n options {\n __typename\n id\n name\n channel\n shippingEstimate {\n value\n unit\n }\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n }\n selectedOptionId\n }\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n ${CartFragment}\n`;\n"],"mappings":";;;;;;;;;AA8BA,MAAa,8BAA8B;AAgE3C,MAAa,0BACX,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA6DD,aAAa"}
1
+ {"version":3,"file":"shipping-operations.js","names":[],"sources":["../../../src/sdk/shipping/shipping-operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { type CartEntity, CartFragment, type CartFreeShippingProgress } from '../cart/operations.js';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * Shipping address mutation documents (docs/storefront-core/reference-free-particle-model.md § The cart write contract).\n *\n * Authored with the hand `gql` tag — the codegen `graphql()` factory is reserved for the reactive\n * read path (`operations.ts`) — and explicitly excluded from the codegen `documents` glob (see\n * `codegen.ts`): the `gql` tag alone is still plucked by `graphql-codegen`, which would duplicate\n * these hand-written types in `sdk/generated/`. The mutation stays hand-typed as a\n * `TypedDocumentNode` (Decision 3 in the spec). The payload projects the full refreshed `shipping`\n * and `cart` state, so the response write into the cache *is* the post-update refresh: `Shipping`'s\n * reactive read flips with no refetch and no imperative cache write.\n *\n * Failures follow ADR-00007's two-path contract: domain outcomes arrive as payload `userErrors`\n * (`ShippingAddressUpdateError`); transport and top-level GraphQL failures are infrastructure,\n * mapped by the handlers in `use-shipping-actions.ts` to the one client-synthesized code,\n * `NETWORK_USER_ERROR`.\n */\n\n/** A typed address-update failure; NETWORK_USER_ERROR is the one client-synthesized code. */\nexport type ShippingAddressUpdateErrorEntity = Readonly<{ code: string; message: string }>;\n\n/**\n * The one client-synthesized error code, carried by the userError that `setAddress` *resolves*\n * (never throws) when the mutation fails at the transport/top-level-GraphQL layer. Consumers use\n * it to route the resolved synthetic failure to the generic upstream error state (Decision 5),\n * distinct from the domain `userErrors` codes that render inline.\n */\nexport const SHIPPING_NETWORK_ERROR_CODE = 'NETWORK_ERROR';\n\nexport type ShippingAddressInput = Readonly<{\n savedAddressId?: string;\n recipientName?: string;\n postalCode: string;\n country: string;\n street?: string;\n number?: string;\n complement?: string;\n neighborhood?: string;\n city?: string;\n state?: string;\n}>;\n\n/** The full shippingAddressUpdate mutation payload. */\nexport type ShippingAddressUpdatePayloadEntity = Readonly<{\n __typename: 'ShippingAddressUpdatePayload';\n shipping: {\n __typename: 'Shipping';\n id: string;\n address: {\n __typename: 'Address';\n id?: string | null;\n recipientName?: string | null;\n postalCode?: string | null;\n country: string;\n street?: string | null;\n number?: string | null;\n complement?: string | null;\n neighborhood?: string | null;\n city?: string | null;\n state?: string | null;\n } | null;\n savedAddresses: ReadonlyArray<{\n __typename: 'Address';\n id?: string | null;\n recipientName?: string | null;\n postalCode?: string | null;\n country: string;\n street?: string | null;\n number?: string | null;\n complement?: string | null;\n neighborhood?: string | null;\n city?: string | null;\n state?: string | null;\n }>;\n options: ReadonlyArray<{\n __typename: 'ShippingOption';\n id: string;\n name: string;\n channel: string;\n shippingEstimate: { __typename: 'ShippingEstimate'; value: number; unit: string };\n price: { __typename: 'Money'; amount: number; currencyCode: string };\n listPrice: { __typename: 'Money'; amount: number; currencyCode: string };\n }>;\n selectedOptionId?: string | null;\n } | null;\n cart: CartEntity & { freeShippingProgress: CartFreeShippingProgress | null };\n userErrors: readonly ShippingAddressUpdateErrorEntity[];\n}>;\n\ntype ShippingAddressUpdateData = Readonly<{ shippingAddressUpdate: ShippingAddressUpdatePayloadEntity }>;\n\nexport const SHIPPING_ADDRESS_UPDATE: TypedDocumentNode<ShippingAddressUpdateData, { input: ShippingAddressInput }> =\n gql`\n mutation ShippingAddressUpdate($input: ShippingAddressUpdateInput!) {\n shippingAddressUpdate(input: $input) {\n shipping {\n __typename\n id\n address {\n __typename\n id\n recipientName\n postalCode\n country\n street\n number\n complement\n neighborhood\n city\n state\n }\n savedAddresses {\n __typename\n id\n recipientName\n postalCode\n country\n street\n number\n complement\n neighborhood\n city\n state\n }\n options {\n __typename\n id\n name\n channel\n shippingEstimate {\n value\n unit\n }\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n }\n selectedOptionId\n }\n cart {\n ...CartFragment\n freeShippingProgress {\n threshold {\n __typename\n amount\n currencyCode\n }\n remaining {\n __typename\n amount\n currencyCode\n }\n qualified\n }\n }\n userErrors {\n code\n message\n }\n }\n }\n ${CartFragment}\n`;\n"],"mappings":";;;;;;;;;AA8BA,MAAa,8BAA8B;AAgE3C,MAAa,0BACX,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0ED,aAAa"}
@@ -2,8 +2,12 @@
2
2
  export declare const orderFormIdVar: import("@apollo/client").ReactiveVar<string | null>;
3
3
  /**
4
4
  * Latest `userErrors` from any cart write, surfaced context-free. The cart action hook publishes
5
- * here on every call (cleared synchronously at the start of the next call) and `CartError` reads it
5
+ * here on every call and `CartError` reads it
6
6
  * via `useReactiveVar`, decoupling error display from `CartRoot`'s data context (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Stays in client state — never a query variable or request header.
7
+ *
8
+ * Lifetime: each item write (add, remove, quantity) clears this var as it starts; coupon writes and
9
+ * `validate` leave it alone. A CartRoot mount clears it too, never a re-render. The var is module-level,
10
+ * so without the mount clear an error from another page would show on the next one.
7
11
  */
8
12
  export declare const cartUserErrorsVar: import("@apollo/client").ReactiveVar<readonly Readonly<{
9
13
  code: string;
@@ -30,8 +34,11 @@ export declare const miniCartOpenVar: import("@apollo/client").ReactiveVar<boole
30
34
  * Latest `userErrors` from a coupon write (`applyCoupon` / `removeCoupon`), surfaced context-free.
31
35
  * A dedicated channel mirroring `cartUserErrorsVar` so coupon feedback sits next to the coupon form
32
36
  * and never collides with item-write errors (docs/storefront-core/reference-free-particle-model.md § The cart write contract). The coupon
33
- * actions publish here on every call (cleared at the start of the next call) and `CartCouponError`
37
+ * actions publish here on every call and `CartCouponError`
34
38
  * reads it via `useReactiveVar`. Stays in client state — never a query variable or request header.
39
+ *
40
+ * Lifetime: each coupon write (apply, remove) clears this var as it starts; item writes and `validate`
41
+ * leave it alone. A CartRoot mount clears it too, never a re-render.
35
42
  */
36
43
  export declare const cartCouponErrorsVar: import("@apollo/client").ReactiveVar<readonly Readonly<{
37
44
  code: string;
@@ -3,8 +3,12 @@ import { makeVar } from "@apollo/client";
3
3
  const orderFormIdVar = makeVar(null);
4
4
  /**
5
5
  * Latest `userErrors` from any cart write, surfaced context-free. The cart action hook publishes
6
- * here on every call (cleared synchronously at the start of the next call) and `CartError` reads it
6
+ * here on every call and `CartError` reads it
7
7
  * via `useReactiveVar`, decoupling error display from `CartRoot`'s data context (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Stays in client state — never a query variable or request header.
8
+ *
9
+ * Lifetime: each item write (add, remove, quantity) clears this var as it starts; coupon writes and
10
+ * `validate` leave it alone. A CartRoot mount clears it too, never a re-render. The var is module-level,
11
+ * so without the mount clear an error from another page would show on the next one.
8
12
  */
9
13
  const cartUserErrorsVar = makeVar([]);
10
14
  /**
@@ -28,8 +32,11 @@ const miniCartOpenVar = makeVar(false);
28
32
  * Latest `userErrors` from a coupon write (`applyCoupon` / `removeCoupon`), surfaced context-free.
29
33
  * A dedicated channel mirroring `cartUserErrorsVar` so coupon feedback sits next to the coupon form
30
34
  * and never collides with item-write errors (docs/storefront-core/reference-free-particle-model.md § The cart write contract). The coupon
31
- * actions publish here on every call (cleared at the start of the next call) and `CartCouponError`
35
+ * actions publish here on every call and `CartCouponError`
32
36
  * reads it via `useReactiveVar`. Stays in client state — never a query variable or request header.
37
+ *
38
+ * Lifetime: each coupon write (apply, remove) clears this var as it starts; item writes and `validate`
39
+ * leave it alone. A CartRoot mount clears it too, never a re-render.
33
40
  */
34
41
  const cartCouponErrorsVar = makeVar([]);
35
42
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"cart.js","names":[],"sources":["../../../src/sdk/state/cart.ts"],"sourcesContent":["import { makeVar } from '@apollo/client';\nimport type { CartUserError } from '../cart/operations.js';\n\n// Session-scoped cart identifier (`Cart.id` from the cart query response). Client state only —\n// never sent as a query variable or request header; the x-order-form-id cookie carries cart\n// context on the transport. Starts null; hydrated when the cart entity arrives, which opens the\n// cart action ready gate. See docs/storefront-core/reference-cart.md § Cart context; docs/storefront-core/reference-free-particle-model.md § The cart write contract.\nexport const orderFormIdVar = makeVar<string | null>(null);\n\n/**\n * Latest `userErrors` from any cart write, surfaced context-free. The cart action hook publishes\n * here on every call (cleared synchronously at the start of the next call) and `CartError` reads it\n * via `useReactiveVar`, decoupling error display from `CartRoot`'s data context (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Stays in client state — never a query variable or request header.\n */\nexport const cartUserErrorsVar = makeVar<readonly CartUserError[]>([]);\n\n/**\n * Monotonic add-success pulse. `addItem` increments it on each successful add (no userErrors); the\n * error and network-catch branches leave it untouched. `CartOpenOnAdd` and `CartRedirectOnAdd` key\n * a `useEffect` on it via `useReactiveVar`, so every successful add re-fires their behavior even when\n * the drawer is already open or was just closed — a boolean would not re-fire on an unchanged value\n * (docs/storefront-core/reference-free-particle-model.md § Read side — state). Client state only — never a query variable\n * or request header.\n */\nexport const cartLastAddVar = makeVar<number>(0);\n\n/**\n * The mini-cart's open state, surfaced context-free. `CartOpenOnAdd` reads it via `useReactiveVar`\n * to control the `DrawerRoot` and writes it back through `onOpenChange` so the header trigger and\n * manual close (Escape / backdrop / close button) stay in sync. The single source of truth for the\n * controlled drawer; `addItem` never writes it. Client state only — never a query variable or\n * request header.\n */\nexport const miniCartOpenVar = makeVar<boolean>(false);\n\n/**\n * Latest `userErrors` from a coupon write (`applyCoupon` / `removeCoupon`), surfaced context-free.\n * A dedicated channel mirroring `cartUserErrorsVar` so coupon feedback sits next to the coupon form\n * and never collides with item-write errors (docs/storefront-core/reference-free-particle-model.md § The cart write contract). The coupon\n * actions publish here on every call (cleared at the start of the next call) and `CartCouponError`\n * reads it via `useReactiveVar`. Stays in client state — never a query variable or request header.\n */\nexport const cartCouponErrorsVar = makeVar<readonly CartUserError[]>([]);\n\n/**\n * Count of cart writes currently in flight (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Every mutating\n * handler in the cart action namespace registers for the lifetime of its call, so `> 0` means a\n * write is outstanding. The stock-validation open call reads this to skip while a write is running —\n * that write's own response already carries current availability, so re-validating would be\n * redundant. `validate` itself is read-only upstream and never registers here. Client state only —\n * never a query variable or request header.\n */\nexport const cartWritesInFlightVar = makeVar<number>(0);\n\n/**\n * Epoch-ms timestamp of when the client last received current availability (`0` = never). Stamped on\n * a successful `validate` and on every successful cart write, because a write's response already\n * carries recomputed availability (docs/storefront-core/reference-cart.md § Validation). The\n * stock-validation open call compares `now - cartAvailabilityFreshAtVar()` against\n * `CART_VALIDATE_STALENESS_MS` and skips while inside the window, so reopening the overlay — or\n * opening it right after an add — does not re-validate. Client state only.\n */\nexport const cartAvailabilityFreshAtVar = makeVar<number>(0);\n\n/**\n * The staleness window for the stock-validation open call, in milliseconds. The single tuning knob:\n * the platform's rate limits under real traffic are not known yet, so this value is a starting point,\n * not a contract (docs/storefront-core/reference-cart.md § Validation). The pre-checkout call ignores it and always\n * runs.\n */\nexport const CART_VALIDATE_STALENESS_MS = 30_000;\n"],"mappings":";;AAOA,MAAa,iBAAiB,QAAuB,IAAI;;;;;;AAOzD,MAAa,oBAAoB,QAAkC,CAAC,CAAC;;;;;;;;;AAUrE,MAAa,iBAAiB,QAAgB,CAAC;;;;;;;;AAS/C,MAAa,kBAAkB,QAAiB,KAAK;;;;;;;;AASrD,MAAa,sBAAsB,QAAkC,CAAC,CAAC;;;;;;;;;AAUvE,MAAa,wBAAwB,QAAgB,CAAC;;;;;;;;;AAUtD,MAAa,6BAA6B,QAAgB,CAAC;;;;;;;AAQ3D,MAAa,6BAA6B"}
1
+ {"version":3,"file":"cart.js","names":[],"sources":["../../../src/sdk/state/cart.ts"],"sourcesContent":["import { makeVar } from '@apollo/client';\nimport type { CartUserError } from '../cart/operations.js';\n\n// Session-scoped cart identifier (`Cart.id` from the cart query response). Client state only —\n// never sent as a query variable or request header; the x-order-form-id cookie carries cart\n// context on the transport. Starts null; hydrated when the cart entity arrives, which opens the\n// cart action ready gate. See docs/storefront-core/reference-cart.md § Cart context; docs/storefront-core/reference-free-particle-model.md § The cart write contract.\nexport const orderFormIdVar = makeVar<string | null>(null);\n\n/**\n * Latest `userErrors` from any cart write, surfaced context-free. The cart action hook publishes\n * here on every call and `CartError` reads it\n * via `useReactiveVar`, decoupling error display from `CartRoot`'s data context (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Stays in client state — never a query variable or request header.\n *\n * Lifetime: each item write (add, remove, quantity) clears this var as it starts; coupon writes and\n * `validate` leave it alone. A CartRoot mount clears it too, never a re-render. The var is module-level,\n * so without the mount clear an error from another page would show on the next one.\n */\nexport const cartUserErrorsVar = makeVar<readonly CartUserError[]>([]);\n\n/**\n * Monotonic add-success pulse. `addItem` increments it on each successful add (no userErrors); the\n * error and network-catch branches leave it untouched. `CartOpenOnAdd` and `CartRedirectOnAdd` key\n * a `useEffect` on it via `useReactiveVar`, so every successful add re-fires their behavior even when\n * the drawer is already open or was just closed — a boolean would not re-fire on an unchanged value\n * (docs/storefront-core/reference-free-particle-model.md § Read side — state). Client state only — never a query variable\n * or request header.\n */\nexport const cartLastAddVar = makeVar<number>(0);\n\n/**\n * The mini-cart's open state, surfaced context-free. `CartOpenOnAdd` reads it via `useReactiveVar`\n * to control the `DrawerRoot` and writes it back through `onOpenChange` so the header trigger and\n * manual close (Escape / backdrop / close button) stay in sync. The single source of truth for the\n * controlled drawer; `addItem` never writes it. Client state only — never a query variable or\n * request header.\n */\nexport const miniCartOpenVar = makeVar<boolean>(false);\n\n/**\n * Latest `userErrors` from a coupon write (`applyCoupon` / `removeCoupon`), surfaced context-free.\n * A dedicated channel mirroring `cartUserErrorsVar` so coupon feedback sits next to the coupon form\n * and never collides with item-write errors (docs/storefront-core/reference-free-particle-model.md § The cart write contract). The coupon\n * actions publish here on every call and `CartCouponError`\n * reads it via `useReactiveVar`. Stays in client state — never a query variable or request header.\n *\n * Lifetime: each coupon write (apply, remove) clears this var as it starts; item writes and `validate`\n * leave it alone. A CartRoot mount clears it too, never a re-render.\n */\nexport const cartCouponErrorsVar = makeVar<readonly CartUserError[]>([]);\n\n/**\n * Count of cart writes currently in flight (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Every mutating\n * handler in the cart action namespace registers for the lifetime of its call, so `> 0` means a\n * write is outstanding. The stock-validation open call reads this to skip while a write is running —\n * that write's own response already carries current availability, so re-validating would be\n * redundant. `validate` itself is read-only upstream and never registers here. Client state only —\n * never a query variable or request header.\n */\nexport const cartWritesInFlightVar = makeVar<number>(0);\n\n/**\n * Epoch-ms timestamp of when the client last received current availability (`0` = never). Stamped on\n * a successful `validate` and on every successful cart write, because a write's response already\n * carries recomputed availability (docs/storefront-core/reference-cart.md § Validation). The\n * stock-validation open call compares `now - cartAvailabilityFreshAtVar()` against\n * `CART_VALIDATE_STALENESS_MS` and skips while inside the window, so reopening the overlay — or\n * opening it right after an add — does not re-validate. Client state only.\n */\nexport const cartAvailabilityFreshAtVar = makeVar<number>(0);\n\n/**\n * The staleness window for the stock-validation open call, in milliseconds. The single tuning knob:\n * the platform's rate limits under real traffic are not known yet, so this value is a starting point,\n * not a contract (docs/storefront-core/reference-cart.md § Validation). The pre-checkout call ignores it and always\n * runs.\n */\nexport const CART_VALIDATE_STALENESS_MS = 30_000;\n"],"mappings":";;AAOA,MAAa,iBAAiB,QAAuB,IAAI;;;;;;;;;;AAWzD,MAAa,oBAAoB,QAAkC,CAAC,CAAC;;;;;;;;;AAUrE,MAAa,iBAAiB,QAAgB,CAAC;;;;;;;;AAS/C,MAAa,kBAAkB,QAAiB,KAAK;;;;;;;;;;;AAYrD,MAAa,sBAAsB,QAAkC,CAAC,CAAC;;;;;;;;;AAUvE,MAAa,wBAAwB,QAAgB,CAAC;;;;;;;;;AAUtD,MAAa,6BAA6B,QAAgB,CAAC;;;;;;;AAQ3D,MAAa,6BAA6B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "FastStore AI Native component library built on Base UI with AI-editable styles",
5
5
  "license": "MIT",
6
6
  "homepage": "https://developers.vtex.com/docs/guides/faststore",
@@ -58,7 +58,7 @@
58
58
  "@base-ui/react": "^1.3.0",
59
59
  "@graphql-typed-document-node/core": "^3.2.0",
60
60
  "@vtex/country-forms": "1.14.0",
61
- "@vtex/faststore-catalog": "0.0.8",
61
+ "@vtex/faststore-catalog": "0.0.10",
62
62
  "esbuild": "~0.27.3",
63
63
  "graphql": "^16.13.1",
64
64
  "react-hook-form": "^7.85.0",
@@ -1,8 +1,7 @@
1
1
  @import "tailwindcss";
2
2
 
3
3
  @theme {
4
- --color-brand: #2953b2;
5
- --color-brand-light: #4a7ae0;
4
+ --color-primary: #2953b2;
6
5
  --color-text: #171a1f;
7
6
  --color-text-inverse: #ffffff;
8
7
  --color-bg: #ffffff;