@vtex/faststore 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/dist/actions.json +181 -0
- package/dist/catalog.json +1595 -481
- package/dist/client.d.ts +2 -1
- package/dist/client.js +2 -1
- package/dist/components.d.ts +31 -7
- package/dist/components.js +35 -13
- package/dist/content-types.json +73 -0
- package/dist/docs/access-code-sign-in.md +1 -1
- package/dist/docs/account-access-gate.md +153 -0
- package/dist/docs/account-entry-point.md +1 -2
- package/dist/docs/account-menu.md +164 -0
- package/dist/docs/address-summary.md +24 -3
- package/dist/docs/alert-dialog.md +204 -0
- package/dist/docs/billing-address.md +284 -0
- package/dist/docs/buy-button.md +1 -1
- package/dist/docs/card-entry.md +250 -0
- package/dist/docs/cardholder-document.md +142 -0
- package/dist/docs/cart.md +1 -1
- package/dist/docs/client-preferences-fieldset.md +148 -0
- package/dist/docs/client-preferences.md +2 -1
- package/dist/docs/client-profile-fieldset.md +184 -0
- package/dist/docs/client-profile.md +1 -1
- package/dist/docs/document-field.md +58 -4
- package/dist/docs/field.md +317 -0
- package/dist/docs/form.md +307 -0
- package/dist/docs/identity-edit.md +6 -6
- package/dist/docs/identity-summary.md +5 -5
- package/dist/docs/installments.md +248 -0
- package/dist/docs/my-account-profile.md +1 -0
- package/dist/docs/order-placed-payment-summary.md +138 -0
- package/dist/docs/payment-method-badges.md +232 -0
- package/dist/docs/payment-methods.md +1 -0
- package/dist/docs/payment.md +218 -0
- package/dist/docs/phone-field.md +58 -4
- package/dist/docs/place-order.md +157 -0
- package/dist/docs/save-card.md +186 -0
- package/dist/docs/saved-cards.md +218 -0
- package/dist/docs/select.md +465 -0
- package/dist/docs/session.md +1 -1
- package/dist/docs/shipping-address.md +110 -258
- package/dist/docs/sign-in-redirect.md +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/internal/client.d.ts +3 -1
- package/dist/internal/client.js +3 -1
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
- package/dist/matter/account-access-gate/account-access-gate.d.ts +86 -0
- package/dist/matter/account-access-gate/account-access-gate.js +283 -0
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -0
- package/dist/matter/account-access-gate/sign-out-intent.d.ts +17 -0
- package/dist/matter/account-access-gate/sign-out-intent.js +62 -0
- package/dist/matter/account-access-gate/sign-out-intent.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.js +2 -2
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +88 -0
- package/dist/matter/account-menu/account-menu.js +246 -0
- package/dist/matter/account-menu/account-menu.js.map +1 -0
- package/dist/matter/billing-address/billing-address.d.ts +165 -0
- package/dist/matter/billing-address/billing-address.js +408 -0
- package/dist/matter/billing-address/billing-address.js.map +1 -0
- package/dist/matter/card-entry/card-capture.d.ts +25 -0
- package/dist/matter/card-entry/card-capture.js +25 -0
- package/dist/matter/card-entry/card-capture.js.map +1 -0
- package/dist/matter/card-entry/card-entry.d.ts +120 -0
- package/dist/matter/card-entry/card-entry.js +832 -0
- package/dist/matter/card-entry/card-entry.js.map +1 -0
- package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
- package/dist/matter/cardholder-document/cardholder-document.js +70 -0
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
- package/dist/matter/cart/cart.d.ts +1 -1
- package/dist/matter/cart/cart.js +2 -2
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
- package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
- package/dist/matter/client-preferences/client-preferences.js +6 -6
- package/dist/matter/client-preferences/client-preferences.js.map +1 -1
- package/dist/matter/client-profile/client-profile.d.ts +4 -6
- package/dist/matter/client-profile/client-profile.js +9 -6
- package/dist/matter/client-profile/client-profile.js.map +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
- package/dist/matter/delivery-options/delivery-options.js.map +1 -1
- package/dist/matter/identity-edit/identity-edit.js +2 -2
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.js +2 -2
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/installments/installments-select.d.ts +42 -0
- package/dist/matter/installments/installments-select.js +167 -0
- package/dist/matter/installments/installments-select.js.map +1 -0
- package/dist/matter/installments/installments.d.ts +58 -0
- package/dist/matter/installments/installments.js +176 -0
- package/dist/matter/installments/installments.js.map +1 -0
- package/dist/matter/installments/use-installments-selection.d.ts +29 -0
- package/dist/matter/installments/use-installments-selection.js +95 -0
- package/dist/matter/installments/use-installments-selection.js.map +1 -0
- package/dist/matter/my-account-profile/my-account-profile.js +5 -5
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.d.ts +19 -0
- package/dist/matter/payment/payment-card-capture.js +129 -0
- package/dist/matter/payment/payment-card-capture.js.map +1 -0
- package/dist/matter/payment/payment-controller.d.ts +24 -0
- package/dist/matter/payment/payment-controller.js +107 -0
- package/dist/matter/payment/payment-controller.js.map +1 -0
- package/dist/matter/payment/payment-method-badges.d.ts +57 -0
- package/dist/matter/payment/payment-method-badges.js +192 -0
- package/dist/matter/payment/payment-method-badges.js.map +1 -0
- package/dist/matter/payment/payment.d.ts +89 -0
- package/dist/matter/payment/payment.js +263 -0
- package/dist/matter/payment/payment.js.map +1 -0
- package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
- package/dist/matter/payment/use-payment-method-selection.js +458 -0
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
- package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
- package/dist/matter/payment/use-payment-submit-gate.js +144 -0
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
- package/dist/matter/place-order/place-order.d.ts +74 -0
- package/dist/matter/place-order/place-order.js +379 -0
- package/dist/matter/place-order/place-order.js.map +1 -0
- package/dist/matter/product-list/product-list.d.ts +1 -0
- package/dist/matter/product-list/product-list.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +1 -0
- package/dist/matter/product-shelf/product-shelf.js +2 -0
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/region/region.js.map +1 -1
- package/dist/matter/save-card/save-card.d.ts +49 -0
- package/dist/matter/save-card/save-card.js +188 -0
- package/dist/matter/save-card/save-card.js.map +1 -0
- package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
- package/dist/matter/saved-cards/saved-cards.js +387 -0
- package/dist/matter/saved-cards/saved-cards.js.map +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +20 -10
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
- package/dist/matter/shipping-address/shipping-address.js +481 -792
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +1 -1
- package/dist/particles/address-summary/address-summary.d.ts +24 -0
- package/dist/particles/address-summary/address-summary.js +83 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
- package/dist/particles/alert-dialog/alert-dialog.js +187 -0
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
- package/dist/particles/checkbox/checkbox.d.ts +7 -3
- package/dist/particles/checkbox/checkbox.js +4 -1
- package/dist/particles/checkbox/checkbox.js.map +1 -1
- package/dist/particles/document-field/document-field.d.ts +48 -5
- package/dist/particles/document-field/document-field.js +189 -32
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/bound-field-registry.js +52 -0
- package/dist/particles/field/bound-field-registry.js.map +1 -0
- package/dist/particles/field/field.d.ts +116 -0
- package/dist/particles/field/field.js +497 -6
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/fieldset/fieldset.js +2 -2
- package/dist/particles/fieldset/fieldset.js.map +1 -1
- package/dist/particles/footer-social/footer-social.js +1 -1
- package/dist/particles/form/form.d.ts +104 -0
- package/dist/particles/form/form.js +485 -0
- package/dist/particles/form/form.js.map +1 -0
- package/dist/particles/incentives/incentives.js +1 -1
- package/dist/particles/phone-field/phone-field.d.ts +38 -10
- package/dist/particles/phone-field/phone-field.js +183 -39
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/postal-code-field/postal-code-field.js +2 -2
- package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
- package/dist/particles/select/field-select.d.ts +43 -0
- package/dist/particles/select/field-select.js +150 -0
- package/dist/particles/select/field-select.js.map +1 -0
- package/dist/particles/select/select.d.ts +2 -0
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/shared/compose-refs.js +21 -0
- package/dist/particles/shared/compose-refs.js.map +1 -0
- package/dist/schema.graphql +701 -2
- package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
- package/dist/sdk/actions/analytics-action-provider.js +19 -0
- package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
- package/dist/sdk/actions/cart-action-provider.d.ts +1 -1
- package/dist/sdk/actions/cart-action-provider.js +1 -1
- package/dist/sdk/actions/cart-action-provider.js.map +1 -1
- package/dist/sdk/actions/index.d.ts +3 -1
- package/dist/sdk/actions/index.js +3 -1
- package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
- package/dist/sdk/actions/payment-action-provider.js +26 -0
- package/dist/sdk/actions/payment-action-provider.js.map +1 -0
- package/dist/sdk/actions/session-action-provider.d.ts +1 -1
- package/dist/sdk/actions/session-action-provider.js +1 -1
- package/dist/sdk/actions/session-action-provider.js.map +1 -1
- package/dist/sdk/actions/shipping-action-provider.d.ts +1 -1
- package/dist/sdk/actions/shipping-action-provider.js +1 -1
- package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
- package/dist/sdk/actions/types.d.ts +6 -2
- package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
- package/dist/sdk/analytics/analytics-destination.js +90 -0
- package/dist/sdk/analytics/analytics-destination.js.map +1 -0
- package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
- package/dist/sdk/analytics/use-analytics-actions.js +108 -0
- package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
- package/dist/sdk/cache/storefront-cache.js +1 -1
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/cart/operations.d.ts +4 -4
- package/dist/sdk/cart/operations.js +1 -1
- package/dist/sdk/cart/operations.js.map +1 -1
- package/dist/sdk/cart/optimistic.js +1 -1
- package/dist/sdk/cart/optimistic.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.d.ts +1 -1
- package/dist/sdk/cart/use-cart-actions.js +4 -4
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
- package/dist/sdk/cart/write-gate.js +1 -1
- package/dist/sdk/cart/write-gate.js.map +1 -1
- package/dist/sdk/data/entity-resolution-context.js +31 -0
- package/dist/sdk/data/entity-resolution-context.js.map +1 -0
- package/dist/sdk/data/entity-subscriber.js +18 -8
- package/dist/sdk/data/entity-subscriber.js.map +1 -1
- package/dist/sdk/data/index.js +1 -0
- package/dist/sdk/data/page-reactive-data-root.d.ts +38 -2
- package/dist/sdk/data/page-reactive-data-root.js +4 -2
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-data-provider.js +8 -4
- package/dist/sdk/data/reactive-data-provider.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +13 -5
- package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
- package/dist/sdk/data/use-reactive-data-settled.js +39 -0
- package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
- package/dist/sdk/data/use-reactive-entity.js +6 -2
- package/dist/sdk/data/use-reactive-entity.js.map +1 -1
- package/dist/sdk/generated/fragment-types.js +2 -0
- package/dist/sdk/generated/fragment-types.js.map +1 -1
- package/dist/sdk/generated/gql.js +2 -1
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.js +85 -1
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +3 -0
- package/dist/sdk/i18n/messages/en-US.json +3 -0
- package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
- package/dist/sdk/i18n/messages/es-ES.js +3 -0
- package/dist/sdk/i18n/messages/es-ES.json +3 -0
- package/dist/sdk/i18n/messages/pt-BR.js +3 -0
- package/dist/sdk/i18n/messages/pt-BR.json +3 -0
- package/dist/sdk/identity/operations.js.map +1 -1
- package/dist/sdk/identity/use-identity-actions.d.ts +1 -1
- package/dist/sdk/identity/use-identity-actions.js +3 -2
- package/dist/sdk/identity/use-identity-actions.js.map +1 -1
- package/dist/sdk/payment/gateway-origin.js +35 -0
- package/dist/sdk/payment/gateway-origin.js.map +1 -0
- package/dist/sdk/payment/gateway.d.ts +85 -0
- package/dist/sdk/payment/gateway.js +183 -0
- package/dist/sdk/payment/gateway.js.map +1 -0
- package/dist/sdk/payment/operations.d.ts +52 -0
- package/dist/sdk/payment/operations.js +223 -0
- package/dist/sdk/payment/operations.js.map +1 -0
- package/dist/sdk/payment/security-code-mask.js +71 -0
- package/dist/sdk/payment/security-code-mask.js.map +1 -0
- package/dist/sdk/payment/submission-registry.d.ts +33 -0
- package/dist/sdk/payment/submission-registry.js +58 -0
- package/dist/sdk/payment/submission-registry.js.map +1 -0
- package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
- package/dist/sdk/payment/use-payment-actions.js +276 -0
- package/dist/sdk/payment/use-payment-actions.js.map +1 -0
- package/dist/sdk/payment/use-place-order.d.ts +65 -0
- package/dist/sdk/payment/use-place-order.js +640 -0
- package/dist/sdk/payment/use-place-order.js.map +1 -0
- package/dist/sdk/session/auth-operations.d.ts +1 -1
- package/dist/sdk/session/auth-operations.js.map +1 -1
- package/dist/sdk/session/use-session-actions.d.ts +1 -1
- package/dist/sdk/session/use-session-actions.js.map +1 -1
- package/dist/sdk/session/use-session-revalidation.js +65 -0
- package/dist/sdk/session/use-session-revalidation.js.map +1 -0
- package/dist/sdk/shipping/option-select-operations.d.ts +1 -1
- package/dist/sdk/shipping/option-select-operations.js.map +1 -1
- package/dist/sdk/shipping/request-timeout.js +1 -1
- package/dist/sdk/shipping/request-timeout.js.map +1 -1
- package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
- package/dist/sdk/shipping/shipping-operations.js.map +1 -1
- package/dist/sdk/shipping/use-shipping-actions.d.ts +2 -2
- package/dist/sdk/shipping/use-shipping-actions.js +1 -1
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
- package/dist/sdk/state/cart.d.ts +3 -4
- package/dist/sdk/state/cart.js +6 -7
- package/dist/sdk/state/cart.js.map +1 -1
- package/dist/utils/loggable-error-name.js +49 -0
- package/dist/utils/loggable-error-name.js.map +1 -0
- package/dist/utils/resolve-authored-copy.js +18 -0
- package/dist/utils/resolve-authored-copy.js.map +1 -0
- package/dist/utils/safe-href.js +3 -3
- package/dist/utils/safe-href.js.map +1 -1
- package/dist/utils/substitute-template.js +33 -0
- package/dist/utils/substitute-template.js.map +1 -0
- package/package.json +2 -1
- package/src/styles/tailwind.css +34 -0
- package/dist/docs/client-profile-form.md +0 -192
- package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
- package/dist/matter/client-profile-form/client-profile-form.js +0 -662
- package/dist/matter/client-profile-form/client-profile-form.js.map +0 -1
|
@@ -3,18 +3,18 @@ import "./operations.js";
|
|
|
3
3
|
import { TypedDocumentNode } from "@apollo/client";
|
|
4
4
|
//#region src/sdk/cart/operations.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* Cart-related GraphQL operations and shared fragments (T6 /
|
|
6
|
+
* Cart-related GraphQL operations and shared fragments (T6 / mutation-contract.md (archived at the specs-archive tag)).
|
|
7
7
|
*
|
|
8
8
|
* The `CartFragment` is the canonical Cart selection set: every mutation operation spreads it so
|
|
9
9
|
* the cache normalisation reaches every reactive subscriber (CartTotal, CartCount, CartItems, …)
|
|
10
10
|
* through a single response. The contract regression test (`contract.test.ts`) parses these
|
|
11
|
-
* documents and asserts the spread is present — see
|
|
11
|
+
* documents and asserts the spread is present — see mutation-contract.md (archived at the specs-archive tag).
|
|
12
12
|
*
|
|
13
|
-
* T7 ([
|
|
13
|
+
* T7 ([Typed documents and the shared cache][T7]) routes the domain types and the operation factory
|
|
14
14
|
* through @graphql-codegen. Public type names (`CartEntity`, `CartItemEntity`, `Money`, payload
|
|
15
15
|
* + input aliases) are preserved as re-exports / aliases so consumer call sites do not change.
|
|
16
16
|
*
|
|
17
|
-
* [T7]: ../../../../../
|
|
17
|
+
* [T7]: ../../../../../docs/storefront-core/reference-free-particle-model.md#typed-documents-and-the-shared-cache
|
|
18
18
|
*/
|
|
19
19
|
export type Money = MoneyFragmentFragment;
|
|
20
20
|
export type CartItemEntity = CartItemFragmentFragment;
|
|
@@ -189,7 +189,7 @@ const CART_COUPON_REMOVE = graphql(`
|
|
|
189
189
|
`);
|
|
190
190
|
/**
|
|
191
191
|
* Re-check the active cart against the platform and adopt corrected availability. Read-only upstream
|
|
192
|
-
* (
|
|
192
|
+
* (docs/storefront-api-core/CONTEXT.md § Language, Cart Validation): the payload is the cart with each line's
|
|
193
193
|
* `variant.availability` refreshed, shaped like every other cart mutation so the correction lands in
|
|
194
194
|
* the normalized cache. Takes no arguments — the cart resolves from the request context. Spreading
|
|
195
195
|
* `CartFragment` is what makes the guard, the per-line alert, and the badge re-render as subscribers,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operations.js","names":[],"sources":["../../../src/sdk/cart/operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { graphql } from '../generated/gql.js';\nimport type {\n CartCouponApplyMutation,\n CartCouponApplyMutationVariables,\n CartCouponRemoveMutation,\n CartCouponRemoveMutationVariables,\n CartFragmentFragment,\n CartItemAddMutation,\n CartItemAddMutationVariables,\n CartItemFragmentFragment,\n CartItemQuantityDecreaseMutation,\n CartItemQuantityDecreaseMutationVariables,\n CartItemQuantityIncreaseMutation,\n CartItemQuantityIncreaseMutationVariables,\n CartItemRemoveMutation,\n CartItemRemoveMutationVariables,\n CartItemsRemoveMutation,\n CartItemsRemoveMutationVariables,\n CartValidateMutation,\n CartValidateMutationVariables,\n MoneyFragmentFragment,\n} from '../generated/graphql.js';\n\n/**\n * Cart-related GraphQL operations and shared fragments (T6 / specs/mutation-contract.md).\n *\n * The `CartFragment` is the canonical Cart selection set: every mutation operation spreads it so\n * the cache normalisation reaches every reactive subscriber (CartTotal, CartCount, CartItems, …)\n * through a single response. The contract regression test (`contract.test.ts`) parses these\n * documents and asserts the spread is present — see specs/mutation-contract.md AD-D3 / AD-8.\n *\n * T7 ([specs/graphql-codegen-pipeline.md][T7]) routes the domain types and the operation factory\n * through @graphql-codegen. Public type names (`CartEntity`, `CartItemEntity`, `Money`, payload\n * + input aliases) are preserved as re-exports / aliases so consumer call sites do not change.\n *\n * [T7]: ../../../../../specs/graphql-codegen-pipeline.md\n */\n\n// ---------------------------------------------------------------------------\n// Domain type aliases — re-exports from codegen artifacts (T7)\n// ---------------------------------------------------------------------------\n\nexport type Money = MoneyFragmentFragment;\nexport type CartItemEntity = CartItemFragmentFragment;\nexport type CartEntity = CartFragmentFragment;\nexport type CartTotalizer = CartFragmentFragment['totalizers'][number];\nexport type CartMessage = CartFragmentFragment['messages'][number];\nexport type AppliedCoupon = CartFragmentFragment['appliedCoupons'][number];\n\nexport type CartUserError = Readonly<{ code: string; message: string }>;\n\n// ---------------------------------------------------------------------------\n// Mutation input types — direct re-exports from codegen\n// ---------------------------------------------------------------------------\n\nexport type {\n CartCouponApplyInput,\n CartCouponRemoveInput,\n CartItemAddInput,\n CartItemQuantityDecreaseInput,\n CartItemQuantityIncreaseInput,\n CartItemRemoveInput,\n CartItemsRemoveInput,\n} from '../generated/graphql.js';\n\n// ---------------------------------------------------------------------------\n// Mutation payload types — derived from operation result types\n// ---------------------------------------------------------------------------\n\nexport type CartItemAddPayload = CartItemAddMutation['cartItemAdd'];\nexport type CartItemRemovePayload = CartItemRemoveMutation['cartItemRemove'];\nexport type CartItemsRemovePayload = CartItemsRemoveMutation['cartItemsRemove'];\nexport type CartItemQuantityIncreasePayload = CartItemQuantityIncreaseMutation['cartItemQuantityIncrease'];\nexport type CartItemQuantityDecreasePayload = CartItemQuantityDecreaseMutation['cartItemQuantityDecrease'];\nexport type CartCouponApplyPayload = CartCouponApplyMutation['cartCouponApply'];\nexport type CartCouponRemovePayload = CartCouponRemoveMutation['cartCouponRemove'];\nexport type CartValidatePayload = CartValidateMutation['cartValidate'];\n\n// ---------------------------------------------------------------------------\n// Shared fragments — authored via the codegen `graphql()` factory so codegen can scan and type\n// every operation that spreads them.\n// ---------------------------------------------------------------------------\n\nexport const MoneyFragment: TypedDocumentNode<MoneyFragmentFragment, never> = graphql(`\n fragment MoneyFragment on Money {\n __typename\n amount\n currencyCode\n }\n`);\n\nexport const CartItemFragment: TypedDocumentNode<CartItemFragmentFragment, never> = graphql(`\n fragment CartItemFragment on CartItem {\n __typename\n id\n quantity\n price {\n ...MoneyFragment\n }\n listPrice {\n ...MoneyFragment\n }\n sellingPrice {\n ...MoneyFragment\n }\n gift\n ownTotal {\n ...MoneyFragment\n }\n total {\n ...MoneyFragment\n }\n variant {\n __typename\n variantId\n sku\n name\n image {\n url\n altText\n }\n availability\n }\n product {\n __typename\n productId\n title\n detailUrl\n categories {\n categoryId\n name\n }\n brand {\n brandId\n name\n }\n }\n }\n`);\n\n/**\n * Canonical Cart selection set. Every cart mutation MUST spread this fragment so the cache\n * receives the entity slices that all subscribers depend on (id + items + totalizers + total +\n * messages). The regression test in contract.test.ts enforces this.\n */\nexport const CartFragment: TypedDocumentNode<CartFragmentFragment, never> = graphql(`\n fragment CartFragment on Cart {\n __typename\n id\n items {\n ...CartItemFragment\n }\n totalizers {\n __typename\n id\n name\n value {\n ...MoneyFragment\n }\n }\n total {\n ...MoneyFragment\n }\n messages {\n __typename\n code\n text\n status\n }\n appliedCoupons {\n code\n isUnmatchedCondition\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Mutation operations — authored via `graphql()`, typed via the codegen-derived\n// TypedDocumentNode generic so Apollo's `useMutation` infers TData / TVariables correctly.\n// ---------------------------------------------------------------------------\n\nexport const CART_ITEM_ADD: TypedDocumentNode<CartItemAddMutation, CartItemAddMutationVariables> = graphql(`\n mutation CartItemAdd($input: CartItemAddInput!) {\n cartItemAdd(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_ITEM_REMOVE: TypedDocumentNode<CartItemRemoveMutation, CartItemRemoveMutationVariables> = graphql(`\n mutation CartItemRemove($input: CartItemRemoveInput!) {\n cartItemRemove(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_ITEMS_REMOVE: TypedDocumentNode<CartItemsRemoveMutation, CartItemsRemoveMutationVariables> = graphql(`\n mutation CartItemsRemove($input: CartItemsRemoveInput!) {\n cartItemsRemove(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_ITEM_QUANTITY_INCREASE: TypedDocumentNode<\n CartItemQuantityIncreaseMutation,\n CartItemQuantityIncreaseMutationVariables\n> = graphql(`\n mutation CartItemQuantityIncrease($input: CartItemQuantityIncreaseInput!) {\n cartItemQuantityIncrease(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_ITEM_QUANTITY_DECREASE: TypedDocumentNode<\n CartItemQuantityDecreaseMutation,\n CartItemQuantityDecreaseMutationVariables\n> = graphql(`\n mutation CartItemQuantityDecrease($input: CartItemQuantityDecreaseInput!) {\n cartItemQuantityDecrease(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_COUPON_APPLY: TypedDocumentNode<CartCouponApplyMutation, CartCouponApplyMutationVariables> = graphql(`\n mutation CartCouponApply($input: CartCouponApplyInput!) {\n cartCouponApply(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_COUPON_REMOVE: TypedDocumentNode<CartCouponRemoveMutation, CartCouponRemoveMutationVariables> =\n graphql(`\n mutation CartCouponRemove($input: CartCouponRemoveInput!) {\n cartCouponRemove(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\n/**\n * Re-check the active cart against the platform and adopt corrected availability. Read-only upstream\n * (specs/mini-cart-validate-operation.md): the payload is the cart with each line's\n * `variant.availability` refreshed, shaped like every other cart mutation so the correction lands in\n * the normalized cache. Takes no arguments — the cart resolves from the request context. Spreading\n * `CartFragment` is what makes the guard, the per-line alert, and the badge re-render as subscribers,\n * so T13 needs no fragment or codegen change beyond registering this document.\n */\nexport const CART_VALIDATE: TypedDocumentNode<CartValidateMutation, CartValidateMutationVariables> = graphql(`\n mutation CartValidate {\n cartValidate {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\n/**\n * Canonical list of cart mutation documents. Used by the contract regression test to iterate\n * over every shipped operation without hard-coding names.\n */\nexport const CART_MUTATIONS = [\n { name: 'CartItemAdd', document: CART_ITEM_ADD },\n { name: 'CartItemRemove', document: CART_ITEM_REMOVE },\n { name: 'CartItemsRemove', document: CART_ITEMS_REMOVE },\n { name: 'CartItemQuantityIncrease', document: CART_ITEM_QUANTITY_INCREASE },\n { name: 'CartItemQuantityDecrease', document: CART_ITEM_QUANTITY_DECREASE },\n { name: 'CartCouponApply', document: CART_COUPON_APPLY },\n { name: 'CartCouponRemove', document: CART_COUPON_REMOVE },\n { name: 'CartValidate', document: CART_VALIDATE },\n] as const;\n"],"mappings":";AAoF8E,QAAQ;;;;;;CAMrF;AAEmF,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+C3F;;;;;;AAOD,MAAa,eAA+D,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BnF;AAOD,MAAa,gBAAsF,QAAQ;;;;;;;;;;;;;CAa1G;AAED,MAAa,mBAA+F,QAAQ;;;;;;;;;;;;;CAanH;AAED,MAAa,oBAAkG,QAAQ;;;;;;;;;;;;;CAatH;AAED,MAAa,8BAGT,QAAQ;;;;;;;;;;;;;CAaX;AAED,MAAa,8BAGT,QAAQ;;;;;;;;;;;;;CAaX;AAED,MAAa,oBAAkG,QAAQ;;;;;;;;;;;;;CAatH;AAED,MAAa,qBACX,QAAQ;;;;;;;;;;;;;CAaT;;;;;;;;;AAUD,MAAa,gBAAwF,QAAQ;;;;;;;;;;;;;CAa5G"}
|
|
1
|
+
{"version":3,"file":"operations.js","names":[],"sources":["../../../src/sdk/cart/operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { graphql } from '../generated/gql.js';\nimport type {\n CartCouponApplyMutation,\n CartCouponApplyMutationVariables,\n CartCouponRemoveMutation,\n CartCouponRemoveMutationVariables,\n CartFragmentFragment,\n CartItemAddMutation,\n CartItemAddMutationVariables,\n CartItemFragmentFragment,\n CartItemQuantityDecreaseMutation,\n CartItemQuantityDecreaseMutationVariables,\n CartItemQuantityIncreaseMutation,\n CartItemQuantityIncreaseMutationVariables,\n CartItemRemoveMutation,\n CartItemRemoveMutationVariables,\n CartItemsRemoveMutation,\n CartItemsRemoveMutationVariables,\n CartValidateMutation,\n CartValidateMutationVariables,\n MoneyFragmentFragment,\n} from '../generated/graphql.js';\n\n/**\n * Cart-related GraphQL operations and shared fragments (T6 / mutation-contract.md (archived at the specs-archive tag)).\n *\n * The `CartFragment` is the canonical Cart selection set: every mutation operation spreads it so\n * the cache normalisation reaches every reactive subscriber (CartTotal, CartCount, CartItems, …)\n * through a single response. The contract regression test (`contract.test.ts`) parses these\n * documents and asserts the spread is present — see mutation-contract.md (archived at the specs-archive tag).\n *\n * T7 ([Typed documents and the shared cache][T7]) routes the domain types and the operation factory\n * through @graphql-codegen. Public type names (`CartEntity`, `CartItemEntity`, `Money`, payload\n * + input aliases) are preserved as re-exports / aliases so consumer call sites do not change.\n *\n * [T7]: ../../../../../docs/storefront-core/reference-free-particle-model.md#typed-documents-and-the-shared-cache\n */\n\n// ---------------------------------------------------------------------------\n// Domain type aliases — re-exports from codegen artifacts (T7)\n// ---------------------------------------------------------------------------\n\nexport type Money = MoneyFragmentFragment;\nexport type CartItemEntity = CartItemFragmentFragment;\nexport type CartEntity = CartFragmentFragment;\nexport type CartTotalizer = CartFragmentFragment['totalizers'][number];\nexport type CartMessage = CartFragmentFragment['messages'][number];\nexport type AppliedCoupon = CartFragmentFragment['appliedCoupons'][number];\n\nexport type CartUserError = Readonly<{ code: string; message: string }>;\n\n// ---------------------------------------------------------------------------\n// Mutation input types — direct re-exports from codegen\n// ---------------------------------------------------------------------------\n\nexport type {\n CartCouponApplyInput,\n CartCouponRemoveInput,\n CartItemAddInput,\n CartItemQuantityDecreaseInput,\n CartItemQuantityIncreaseInput,\n CartItemRemoveInput,\n CartItemsRemoveInput,\n} from '../generated/graphql.js';\n\n// ---------------------------------------------------------------------------\n// Mutation payload types — derived from operation result types\n// ---------------------------------------------------------------------------\n\nexport type CartItemAddPayload = CartItemAddMutation['cartItemAdd'];\nexport type CartItemRemovePayload = CartItemRemoveMutation['cartItemRemove'];\nexport type CartItemsRemovePayload = CartItemsRemoveMutation['cartItemsRemove'];\nexport type CartItemQuantityIncreasePayload = CartItemQuantityIncreaseMutation['cartItemQuantityIncrease'];\nexport type CartItemQuantityDecreasePayload = CartItemQuantityDecreaseMutation['cartItemQuantityDecrease'];\nexport type CartCouponApplyPayload = CartCouponApplyMutation['cartCouponApply'];\nexport type CartCouponRemovePayload = CartCouponRemoveMutation['cartCouponRemove'];\nexport type CartValidatePayload = CartValidateMutation['cartValidate'];\n\n// ---------------------------------------------------------------------------\n// Shared fragments — authored via the codegen `graphql()` factory so codegen can scan and type\n// every operation that spreads them.\n// ---------------------------------------------------------------------------\n\nexport const MoneyFragment: TypedDocumentNode<MoneyFragmentFragment, never> = graphql(`\n fragment MoneyFragment on Money {\n __typename\n amount\n currencyCode\n }\n`);\n\nexport const CartItemFragment: TypedDocumentNode<CartItemFragmentFragment, never> = graphql(`\n fragment CartItemFragment on CartItem {\n __typename\n id\n quantity\n price {\n ...MoneyFragment\n }\n listPrice {\n ...MoneyFragment\n }\n sellingPrice {\n ...MoneyFragment\n }\n gift\n ownTotal {\n ...MoneyFragment\n }\n total {\n ...MoneyFragment\n }\n variant {\n __typename\n variantId\n sku\n name\n image {\n url\n altText\n }\n availability\n }\n product {\n __typename\n productId\n title\n detailUrl\n categories {\n categoryId\n name\n }\n brand {\n brandId\n name\n }\n }\n }\n`);\n\n/**\n * Canonical Cart selection set. Every cart mutation MUST spread this fragment so the cache\n * receives the entity slices that all subscribers depend on (id + items + totalizers + total +\n * messages). The regression test in contract.test.ts enforces this.\n */\nexport const CartFragment: TypedDocumentNode<CartFragmentFragment, never> = graphql(`\n fragment CartFragment on Cart {\n __typename\n id\n items {\n ...CartItemFragment\n }\n totalizers {\n __typename\n id\n name\n value {\n ...MoneyFragment\n }\n }\n total {\n ...MoneyFragment\n }\n messages {\n __typename\n code\n text\n status\n }\n appliedCoupons {\n code\n isUnmatchedCondition\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Mutation operations — authored via `graphql()`, typed via the codegen-derived\n// TypedDocumentNode generic so Apollo's `useMutation` infers TData / TVariables correctly.\n// ---------------------------------------------------------------------------\n\nexport const CART_ITEM_ADD: TypedDocumentNode<CartItemAddMutation, CartItemAddMutationVariables> = graphql(`\n mutation CartItemAdd($input: CartItemAddInput!) {\n cartItemAdd(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_ITEM_REMOVE: TypedDocumentNode<CartItemRemoveMutation, CartItemRemoveMutationVariables> = graphql(`\n mutation CartItemRemove($input: CartItemRemoveInput!) {\n cartItemRemove(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_ITEMS_REMOVE: TypedDocumentNode<CartItemsRemoveMutation, CartItemsRemoveMutationVariables> = graphql(`\n mutation CartItemsRemove($input: CartItemsRemoveInput!) {\n cartItemsRemove(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_ITEM_QUANTITY_INCREASE: TypedDocumentNode<\n CartItemQuantityIncreaseMutation,\n CartItemQuantityIncreaseMutationVariables\n> = graphql(`\n mutation CartItemQuantityIncrease($input: CartItemQuantityIncreaseInput!) {\n cartItemQuantityIncrease(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_ITEM_QUANTITY_DECREASE: TypedDocumentNode<\n CartItemQuantityDecreaseMutation,\n CartItemQuantityDecreaseMutationVariables\n> = graphql(`\n mutation CartItemQuantityDecrease($input: CartItemQuantityDecreaseInput!) {\n cartItemQuantityDecrease(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_COUPON_APPLY: TypedDocumentNode<CartCouponApplyMutation, CartCouponApplyMutationVariables> = graphql(`\n mutation CartCouponApply($input: CartCouponApplyInput!) {\n cartCouponApply(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\nexport const CART_COUPON_REMOVE: TypedDocumentNode<CartCouponRemoveMutation, CartCouponRemoveMutationVariables> =\n graphql(`\n mutation CartCouponRemove($input: CartCouponRemoveInput!) {\n cartCouponRemove(input: $input) {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\n/**\n * Re-check the active cart against the platform and adopt corrected availability. Read-only upstream\n * (docs/storefront-api-core/CONTEXT.md § Language, Cart Validation): the payload is the cart with each line's\n * `variant.availability` refreshed, shaped like every other cart mutation so the correction lands in\n * the normalized cache. Takes no arguments — the cart resolves from the request context. Spreading\n * `CartFragment` is what makes the guard, the per-line alert, and the badge re-render as subscribers,\n * so T13 needs no fragment or codegen change beyond registering this document.\n */\nexport const CART_VALIDATE: TypedDocumentNode<CartValidateMutation, CartValidateMutationVariables> = graphql(`\n mutation CartValidate {\n cartValidate {\n __typename\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n`);\n\n/**\n * Canonical list of cart mutation documents. Used by the contract regression test to iterate\n * over every shipped operation without hard-coding names.\n */\nexport const CART_MUTATIONS = [\n { name: 'CartItemAdd', document: CART_ITEM_ADD },\n { name: 'CartItemRemove', document: CART_ITEM_REMOVE },\n { name: 'CartItemsRemove', document: CART_ITEMS_REMOVE },\n { name: 'CartItemQuantityIncrease', document: CART_ITEM_QUANTITY_INCREASE },\n { name: 'CartItemQuantityDecrease', document: CART_ITEM_QUANTITY_DECREASE },\n { name: 'CartCouponApply', document: CART_COUPON_APPLY },\n { name: 'CartCouponRemove', document: CART_COUPON_REMOVE },\n { name: 'CartValidate', document: CART_VALIDATE },\n] as const;\n"],"mappings":";AAoF8E,QAAQ;;;;;;CAMrF;AAEmF,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+C3F;;;;;;AAOD,MAAa,eAA+D,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BnF;AAOD,MAAa,gBAAsF,QAAQ;;;;;;;;;;;;;CAa1G;AAED,MAAa,mBAA+F,QAAQ;;;;;;;;;;;;;CAanH;AAED,MAAa,oBAAkG,QAAQ;;;;;;;;;;;;;CAatH;AAED,MAAa,8BAGT,QAAQ;;;;;;;;;;;;;CAaX;AAED,MAAa,8BAGT,QAAQ;;;;;;;;;;;;;CAaX;AAED,MAAa,oBAAkG,QAAQ;;;;;;;;;;;;;CAatH;AAED,MAAa,qBACX,QAAQ;;;;;;;;;;;;;CAaT;;;;;;;;;AAUD,MAAa,gBAAwF,QAAQ;;;;;;;;;;;;;CAa5G"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/sdk/cart/optimistic.ts
|
|
2
2
|
/**
|
|
3
3
|
* Pure optimistic-response builders for the three cache-propagating cart mutations
|
|
4
|
-
* (
|
|
4
|
+
* (mutation-contract.md (archived at the specs-archive tag)).
|
|
5
5
|
*
|
|
6
6
|
* Each builder takes the current `CartEntity` and the affected `CartItemEntity` and returns the
|
|
7
7
|
* predicted post-mutation `Cart` carrying every field the canonical `CartFragment` selects:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimistic.js","names":[],"sources":["../../../src/sdk/cart/optimistic.ts"],"sourcesContent":["import type {\n CartEntity,\n CartItemEntity,\n CartItemQuantityDecreasePayload,\n CartItemQuantityIncreasePayload,\n CartItemRemovePayload,\n CartItemsRemovePayload,\n CartTotalizer,\n Money,\n} from './operations.js';\n\n/**\n * Pure optimistic-response builders for the three cache-propagating cart mutations\n * (specs/mutation-contract.md AD-3 / AD-D6).\n *\n * Each builder takes the current `CartEntity` and the affected `CartItemEntity` and returns the\n * predicted post-mutation `Cart` carrying every field the canonical `CartFragment` selects:\n * `id`, `items`, `totalizers`, `total`. The shape MUST match Apollo's normalised cache slot for\n * the entity, so `__typename` and the same `id` are preserved.\n *\n * Lines are matched by their opaque `id` (Base64(\"CartItem:<orderFormId>:<uniqueId>\")).\n *\n * Recomputation rules (kept simple — server reconciles drift on response):\n * - `Items` totalizer (matched by `id === 'Items'`) is adjusted by `item.sellingPrice * delta`.\n * - `total` (the cart's grand total) is adjusted by the same delta.\n * - All other totalizers (Shipping, Discounts, …) stay as-is until the server response lands.\n */\n\nfunction moneyAdd(money: Money, delta: number): Money {\n // `Money.amount` is typed as `number` by codegen but arrives over the wire as a decimal *string*\n // (the server's `Decimal` scalar = `(cents / 100).toFixed(2)`). Without coercing, `string + number`\n // concatenates (e.g. \"49.99\" + 49.99 → \"49.9949.99\"), which formats to NaN during the optimistic\n // window until the server response replaces it. Number() restores numeric addition.\n return { __typename: 'Money', amount: Number(money.amount) + delta, currencyCode: money.currencyCode };\n}\n\nfunction adjustItemsTotalizer(totalizers: readonly CartTotalizer[], delta: number): CartTotalizer[] {\n return totalizers.map((t) => (t.id === 'Items' ? { ...t, value: moneyAdd(t.value, delta) } : t));\n}\n\nfunction emptyUserErrors<T>(): T[] {\n return [];\n}\n\n/**\n * Predicts the cart after `cartItemQuantityIncrease(input: { variantId, quantityToAdd })`.\n */\nexport function buildOptimisticIncrease(\n cart: CartEntity,\n item: CartItemEntity,\n by = 1,\n): { cartItemQuantityIncrease: CartItemQuantityIncreasePayload } {\n if (by <= 0) {\n return {\n cartItemQuantityIncrease: {\n __typename: 'CartItemQuantityIncreasePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const currentItem = cart.items.find((i) => i.id === item.id);\n if (!currentItem) {\n return {\n cartItemQuantityIncrease: {\n __typename: 'CartItemQuantityIncreasePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const newQuantity = currentItem.quantity + by;\n const delta = currentItem.sellingPrice.amount * by;\n\n const updatedItems = cart.items.map((i) => (i.id === item.id ? { ...i, quantity: newQuantity } : i));\n\n return {\n cartItemQuantityIncrease: {\n __typename: 'CartItemQuantityIncreasePayload',\n cart: {\n __typename: 'Cart',\n id: cart.id,\n items: updatedItems,\n totalizers: adjustItemsTotalizer(cart.totalizers, delta),\n total: moneyAdd(cart.total, delta),\n messages: cart.messages,\n appliedCoupons: cart.appliedCoupons,\n },\n userErrors: emptyUserErrors(),\n },\n };\n}\n\n/**\n * Predicts the cart after `cartItemQuantityDecrease(input: { variantId, quantityToRemove })`.\n *\n * If the resulting quantity would be `<= 0`, the line is removed entirely.\n */\nexport function buildOptimisticDecrease(\n cart: CartEntity,\n item: CartItemEntity,\n by = 1,\n): { cartItemQuantityDecrease: CartItemQuantityDecreasePayload } {\n if (by <= 0) {\n return {\n cartItemQuantityDecrease: {\n __typename: 'CartItemQuantityDecreasePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const currentItem = cart.items.find((i) => i.id === item.id);\n if (!currentItem) {\n return {\n cartItemQuantityDecrease: {\n __typename: 'CartItemQuantityDecreasePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const requestedQuantity = currentItem.quantity - by;\n const removed = requestedQuantity <= 0;\n const actualDelta = removed ? currentItem.quantity : by;\n const priceDelta = -currentItem.sellingPrice.amount * actualDelta;\n\n const updatedItems = removed\n ? cart.items.filter((i) => i.id !== item.id)\n : cart.items.map((i) => (i.id === item.id ? { ...i, quantity: requestedQuantity } : i));\n\n return {\n cartItemQuantityDecrease: {\n __typename: 'CartItemQuantityDecreasePayload',\n cart: {\n __typename: 'Cart',\n id: cart.id,\n items: updatedItems,\n totalizers: adjustItemsTotalizer(cart.totalizers, priceDelta),\n total: moneyAdd(cart.total, priceDelta),\n messages: cart.messages,\n appliedCoupons: cart.appliedCoupons,\n },\n userErrors: emptyUserErrors(),\n },\n };\n}\n\n/**\n * Predicts the cart after `cartItemRemove(input: { variantId })`.\n */\nexport function buildOptimisticRemove(\n cart: CartEntity,\n item: CartItemEntity,\n): { cartItemRemove: CartItemRemovePayload } {\n const currentItem = cart.items.find((i) => i.id === item.id);\n if (!currentItem) {\n return {\n cartItemRemove: {\n __typename: 'CartItemRemovePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const priceDelta = -currentItem.sellingPrice.amount * currentItem.quantity;\n\n return {\n cartItemRemove: {\n __typename: 'CartItemRemovePayload',\n cart: {\n __typename: 'Cart',\n id: cart.id,\n items: cart.items.filter((i) => i.id !== item.id),\n totalizers: adjustItemsTotalizer(cart.totalizers, priceDelta),\n total: moneyAdd(cart.total, priceDelta),\n messages: cart.messages,\n appliedCoupons: cart.appliedCoupons,\n },\n userErrors: emptyUserErrors(),\n },\n };\n}\n\n/**\n * Predicts the cart after `cartItemsRemove(input: { variantIds })` — the N-line generalisation of\n * `buildOptimisticRemove`. Lines absent from the cart are ignored; the summed price delta of the\n * matched lines adjusts the `Items` totalizer and the grand total.\n */\nexport function buildOptimisticRemoveItems(\n cart: CartEntity,\n items: readonly CartItemEntity[],\n): { cartItemsRemove: CartItemsRemovePayload } {\n const removedIds = new Set(items.map((item) => item.id));\n const removedLines = cart.items.filter((i) => removedIds.has(i.id));\n if (removedLines.length === 0) {\n return {\n cartItemsRemove: {\n __typename: 'CartItemsRemovePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const priceDelta = removedLines.reduce((sum, line) => sum - line.sellingPrice.amount * line.quantity, 0);\n\n return {\n cartItemsRemove: {\n __typename: 'CartItemsRemovePayload',\n cart: {\n __typename: 'Cart',\n id: cart.id,\n items: cart.items.filter((i) => !removedIds.has(i.id)),\n totalizers: adjustItemsTotalizer(cart.totalizers, priceDelta),\n total: moneyAdd(cart.total, priceDelta),\n messages: cart.messages,\n appliedCoupons: cart.appliedCoupons,\n },\n userErrors: emptyUserErrors(),\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"optimistic.js","names":[],"sources":["../../../src/sdk/cart/optimistic.ts"],"sourcesContent":["import type {\n CartEntity,\n CartItemEntity,\n CartItemQuantityDecreasePayload,\n CartItemQuantityIncreasePayload,\n CartItemRemovePayload,\n CartItemsRemovePayload,\n CartTotalizer,\n Money,\n} from './operations.js';\n\n/**\n * Pure optimistic-response builders for the three cache-propagating cart mutations\n * (mutation-contract.md (archived at the specs-archive tag)).\n *\n * Each builder takes the current `CartEntity` and the affected `CartItemEntity` and returns the\n * predicted post-mutation `Cart` carrying every field the canonical `CartFragment` selects:\n * `id`, `items`, `totalizers`, `total`. The shape MUST match Apollo's normalised cache slot for\n * the entity, so `__typename` and the same `id` are preserved.\n *\n * Lines are matched by their opaque `id` (Base64(\"CartItem:<orderFormId>:<uniqueId>\")).\n *\n * Recomputation rules (kept simple — server reconciles drift on response):\n * - `Items` totalizer (matched by `id === 'Items'`) is adjusted by `item.sellingPrice * delta`.\n * - `total` (the cart's grand total) is adjusted by the same delta.\n * - All other totalizers (Shipping, Discounts, …) stay as-is until the server response lands.\n */\n\nfunction moneyAdd(money: Money, delta: number): Money {\n // `Money.amount` is typed as `number` by codegen but arrives over the wire as a decimal *string*\n // (the server's `Decimal` scalar = `(cents / 100).toFixed(2)`). Without coercing, `string + number`\n // concatenates (e.g. \"49.99\" + 49.99 → \"49.9949.99\"), which formats to NaN during the optimistic\n // window until the server response replaces it. Number() restores numeric addition.\n return { __typename: 'Money', amount: Number(money.amount) + delta, currencyCode: money.currencyCode };\n}\n\nfunction adjustItemsTotalizer(totalizers: readonly CartTotalizer[], delta: number): CartTotalizer[] {\n return totalizers.map((t) => (t.id === 'Items' ? { ...t, value: moneyAdd(t.value, delta) } : t));\n}\n\nfunction emptyUserErrors<T>(): T[] {\n return [];\n}\n\n/**\n * Predicts the cart after `cartItemQuantityIncrease(input: { variantId, quantityToAdd })`.\n */\nexport function buildOptimisticIncrease(\n cart: CartEntity,\n item: CartItemEntity,\n by = 1,\n): { cartItemQuantityIncrease: CartItemQuantityIncreasePayload } {\n if (by <= 0) {\n return {\n cartItemQuantityIncrease: {\n __typename: 'CartItemQuantityIncreasePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const currentItem = cart.items.find((i) => i.id === item.id);\n if (!currentItem) {\n return {\n cartItemQuantityIncrease: {\n __typename: 'CartItemQuantityIncreasePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const newQuantity = currentItem.quantity + by;\n const delta = currentItem.sellingPrice.amount * by;\n\n const updatedItems = cart.items.map((i) => (i.id === item.id ? { ...i, quantity: newQuantity } : i));\n\n return {\n cartItemQuantityIncrease: {\n __typename: 'CartItemQuantityIncreasePayload',\n cart: {\n __typename: 'Cart',\n id: cart.id,\n items: updatedItems,\n totalizers: adjustItemsTotalizer(cart.totalizers, delta),\n total: moneyAdd(cart.total, delta),\n messages: cart.messages,\n appliedCoupons: cart.appliedCoupons,\n },\n userErrors: emptyUserErrors(),\n },\n };\n}\n\n/**\n * Predicts the cart after `cartItemQuantityDecrease(input: { variantId, quantityToRemove })`.\n *\n * If the resulting quantity would be `<= 0`, the line is removed entirely.\n */\nexport function buildOptimisticDecrease(\n cart: CartEntity,\n item: CartItemEntity,\n by = 1,\n): { cartItemQuantityDecrease: CartItemQuantityDecreasePayload } {\n if (by <= 0) {\n return {\n cartItemQuantityDecrease: {\n __typename: 'CartItemQuantityDecreasePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const currentItem = cart.items.find((i) => i.id === item.id);\n if (!currentItem) {\n return {\n cartItemQuantityDecrease: {\n __typename: 'CartItemQuantityDecreasePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const requestedQuantity = currentItem.quantity - by;\n const removed = requestedQuantity <= 0;\n const actualDelta = removed ? currentItem.quantity : by;\n const priceDelta = -currentItem.sellingPrice.amount * actualDelta;\n\n const updatedItems = removed\n ? cart.items.filter((i) => i.id !== item.id)\n : cart.items.map((i) => (i.id === item.id ? { ...i, quantity: requestedQuantity } : i));\n\n return {\n cartItemQuantityDecrease: {\n __typename: 'CartItemQuantityDecreasePayload',\n cart: {\n __typename: 'Cart',\n id: cart.id,\n items: updatedItems,\n totalizers: adjustItemsTotalizer(cart.totalizers, priceDelta),\n total: moneyAdd(cart.total, priceDelta),\n messages: cart.messages,\n appliedCoupons: cart.appliedCoupons,\n },\n userErrors: emptyUserErrors(),\n },\n };\n}\n\n/**\n * Predicts the cart after `cartItemRemove(input: { variantId })`.\n */\nexport function buildOptimisticRemove(\n cart: CartEntity,\n item: CartItemEntity,\n): { cartItemRemove: CartItemRemovePayload } {\n const currentItem = cart.items.find((i) => i.id === item.id);\n if (!currentItem) {\n return {\n cartItemRemove: {\n __typename: 'CartItemRemovePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const priceDelta = -currentItem.sellingPrice.amount * currentItem.quantity;\n\n return {\n cartItemRemove: {\n __typename: 'CartItemRemovePayload',\n cart: {\n __typename: 'Cart',\n id: cart.id,\n items: cart.items.filter((i) => i.id !== item.id),\n totalizers: adjustItemsTotalizer(cart.totalizers, priceDelta),\n total: moneyAdd(cart.total, priceDelta),\n messages: cart.messages,\n appliedCoupons: cart.appliedCoupons,\n },\n userErrors: emptyUserErrors(),\n },\n };\n}\n\n/**\n * Predicts the cart after `cartItemsRemove(input: { variantIds })` — the N-line generalisation of\n * `buildOptimisticRemove`. Lines absent from the cart are ignored; the summed price delta of the\n * matched lines adjusts the `Items` totalizer and the grand total.\n */\nexport function buildOptimisticRemoveItems(\n cart: CartEntity,\n items: readonly CartItemEntity[],\n): { cartItemsRemove: CartItemsRemovePayload } {\n const removedIds = new Set(items.map((item) => item.id));\n const removedLines = cart.items.filter((i) => removedIds.has(i.id));\n if (removedLines.length === 0) {\n return {\n cartItemsRemove: {\n __typename: 'CartItemsRemovePayload',\n cart,\n userErrors: emptyUserErrors(),\n },\n };\n }\n\n const priceDelta = removedLines.reduce((sum, line) => sum - line.sellingPrice.amount * line.quantity, 0);\n\n return {\n cartItemsRemove: {\n __typename: 'CartItemsRemovePayload',\n cart: {\n __typename: 'Cart',\n id: cart.id,\n items: cart.items.filter((i) => !removedIds.has(i.id)),\n totalizers: adjustItemsTotalizer(cart.totalizers, priceDelta),\n total: moneyAdd(cart.total, priceDelta),\n messages: cart.messages,\n appliedCoupons: cart.appliedCoupons,\n },\n userErrors: emptyUserErrors(),\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA4BA,SAAS,SAAS,OAAc,OAAsB;CAKpD,OAAO;EAAE,YAAY;EAAS,QAAQ,OAAO,MAAM,MAAM,IAAI;EAAO,cAAc,MAAM;CAAa;AACvG;AAEA,SAAS,qBAAqB,YAAsC,OAAgC;CAClG,OAAO,WAAW,KAAK,MAAO,EAAE,OAAO,UAAU;EAAE,GAAG;EAAG,OAAO,SAAS,EAAE,OAAO,KAAK;CAAE,IAAI,CAAE;AACjG;AAEA,SAAS,kBAA0B;CACjC,OAAO,CAAC;AACV;;;;AAKA,SAAgB,wBACd,MACA,MACA,KAAK,GAC0D;CAC/D,IAAI,MAAM,GACR,OAAO,EACL,0BAA0B;EACxB,YAAY;EACZ;EACA,YAAY,gBAAgB;CAC9B,EACF;CAGF,MAAM,cAAc,KAAK,MAAM,MAAM,MAAM,EAAE,OAAO,KAAK,EAAE;CAC3D,IAAI,CAAC,aACH,OAAO,EACL,0BAA0B;EACxB,YAAY;EACZ;EACA,YAAY,gBAAgB;CAC9B,EACF;CAGF,MAAM,cAAc,YAAY,WAAW;CAC3C,MAAM,QAAQ,YAAY,aAAa,SAAS;CAEhD,MAAM,eAAe,KAAK,MAAM,KAAK,MAAO,EAAE,OAAO,KAAK,KAAK;EAAE,GAAG;EAAG,UAAU;CAAY,IAAI,CAAE;CAEnG,OAAO,EACL,0BAA0B;EACxB,YAAY;EACZ,MAAM;GACJ,YAAY;GACZ,IAAI,KAAK;GACT,OAAO;GACP,YAAY,qBAAqB,KAAK,YAAY,KAAK;GACvD,OAAO,SAAS,KAAK,OAAO,KAAK;GACjC,UAAU,KAAK;GACf,gBAAgB,KAAK;EACvB;EACA,YAAY,gBAAgB;CAC9B,EACF;AACF;;;;;;AAOA,SAAgB,wBACd,MACA,MACA,KAAK,GAC0D;CAC/D,IAAI,MAAM,GACR,OAAO,EACL,0BAA0B;EACxB,YAAY;EACZ;EACA,YAAY,gBAAgB;CAC9B,EACF;CAGF,MAAM,cAAc,KAAK,MAAM,MAAM,MAAM,EAAE,OAAO,KAAK,EAAE;CAC3D,IAAI,CAAC,aACH,OAAO,EACL,0BAA0B;EACxB,YAAY;EACZ;EACA,YAAY,gBAAgB;CAC9B,EACF;CAGF,MAAM,oBAAoB,YAAY,WAAW;CACjD,MAAM,UAAU,qBAAqB;CACrC,MAAM,cAAc,UAAU,YAAY,WAAW;CACrD,MAAM,aAAa,CAAC,YAAY,aAAa,SAAS;CAEtD,MAAM,eAAe,UACjB,KAAK,MAAM,QAAQ,MAAM,EAAE,OAAO,KAAK,EAAE,IACzC,KAAK,MAAM,KAAK,MAAO,EAAE,OAAO,KAAK,KAAK;EAAE,GAAG;EAAG,UAAU;CAAkB,IAAI,CAAE;CAExF,OAAO,EACL,0BAA0B;EACxB,YAAY;EACZ,MAAM;GACJ,YAAY;GACZ,IAAI,KAAK;GACT,OAAO;GACP,YAAY,qBAAqB,KAAK,YAAY,UAAU;GAC5D,OAAO,SAAS,KAAK,OAAO,UAAU;GACtC,UAAU,KAAK;GACf,gBAAgB,KAAK;EACvB;EACA,YAAY,gBAAgB;CAC9B,EACF;AACF;;;;AAKA,SAAgB,sBACd,MACA,MAC2C;CAC3C,MAAM,cAAc,KAAK,MAAM,MAAM,MAAM,EAAE,OAAO,KAAK,EAAE;CAC3D,IAAI,CAAC,aACH,OAAO,EACL,gBAAgB;EACd,YAAY;EACZ;EACA,YAAY,gBAAgB;CAC9B,EACF;CAGF,MAAM,aAAa,CAAC,YAAY,aAAa,SAAS,YAAY;CAElE,OAAO,EACL,gBAAgB;EACd,YAAY;EACZ,MAAM;GACJ,YAAY;GACZ,IAAI,KAAK;GACT,OAAO,KAAK,MAAM,QAAQ,MAAM,EAAE,OAAO,KAAK,EAAE;GAChD,YAAY,qBAAqB,KAAK,YAAY,UAAU;GAC5D,OAAO,SAAS,KAAK,OAAO,UAAU;GACtC,UAAU,KAAK;GACf,gBAAgB,KAAK;EACvB;EACA,YAAY,gBAAgB;CAC9B,EACF;AACF;;;;;;AAOA,SAAgB,2BACd,MACA,OAC6C;CAC7C,MAAM,aAAa,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,EAAE,CAAC;CACvD,MAAM,eAAe,KAAK,MAAM,QAAQ,MAAM,WAAW,IAAI,EAAE,EAAE,CAAC;CAClE,IAAI,aAAa,WAAW,GAC1B,OAAO,EACL,iBAAiB;EACf,YAAY;EACZ;EACA,YAAY,gBAAgB;CAC9B,EACF;CAGF,MAAM,aAAa,aAAa,QAAQ,KAAK,SAAS,MAAM,KAAK,aAAa,SAAS,KAAK,UAAU,CAAC;CAEvG,OAAO,EACL,iBAAiB;EACf,YAAY;EACZ,MAAM;GACJ,YAAY;GACZ,IAAI,KAAK;GACT,OAAO,KAAK,MAAM,QAAQ,MAAM,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;GACrD,YAAY,qBAAqB,KAAK,YAAY,UAAU;GAC5D,OAAO,SAAS,KAAK,OAAO,UAAU;GACtC,UAAU,KAAK;GACf,gBAAgB,KAAK;EACvB;EACA,YAAY,gBAAgB;CAC9B,EACF;AACF"}
|
|
@@ -27,7 +27,7 @@ export type CartCouponPayload = Readonly<{
|
|
|
27
27
|
/**
|
|
28
28
|
* The `cart` namespace surface. Item writes return the resulting `userErrors` (empty on success)
|
|
29
29
|
* and publish them to `cartUserErrorsVar`; coupon writes publish to the dedicated
|
|
30
|
-
* `cartCouponErrorsVar` (
|
|
30
|
+
* `cartCouponErrorsVar` (docs/storefront-core/reference-free-particle-model.md § The cart write contract).
|
|
31
31
|
*/
|
|
32
32
|
export type CartActions = Readonly<{
|
|
33
33
|
/** Add an item to the cart. Async (no optimistic response). Creates the cart on first add. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { defineActions } from "../../core/dist/actions/registry.js";
|
|
3
3
|
import "../../core/dist/index.js";
|
|
4
|
-
import { cartCouponErrorsVar, cartLastAddVar, cartUserErrorsVar, orderFormIdVar } from "../state/cart.js";
|
|
5
4
|
import { CART_COUPON_APPLY, CART_COUPON_REMOVE, CART_ITEMS_REMOVE, CART_ITEM_ADD, CART_ITEM_QUANTITY_DECREASE, CART_ITEM_QUANTITY_INCREASE, CART_ITEM_REMOVE, CART_VALIDATE, CartFragment } from "./operations.js";
|
|
5
|
+
import { cartCouponErrorsVar, cartLastAddVar, cartUserErrorsVar, orderFormIdVar } from "../state/cart.js";
|
|
6
6
|
import { buildOptimisticDecrease, buildOptimisticIncrease, buildOptimisticRemove, buildOptimisticRemoveItems } from "./optimistic.js";
|
|
7
7
|
import { markAvailabilityFresh, trackCartWrite } from "./write-gate.js";
|
|
8
8
|
import { useApolloClient, useMutation } from "@apollo/client/react";
|
|
@@ -10,7 +10,7 @@ import { useCallback, useMemo } from "react";
|
|
|
10
10
|
//#region src/sdk/cart/use-cart-actions.ts
|
|
11
11
|
/**
|
|
12
12
|
* The `cart` action namespace — the consolidated, context-free owner of every cart write
|
|
13
|
-
* (
|
|
13
|
+
* (docs/storefront-core/reference-free-particle-model.md § The cart write contract, docs/storefront-core/adr-0010-actions-own-the-write-side.md).
|
|
14
14
|
*
|
|
15
15
|
* It replaces `useCartMutations` + the mutation handlers `CartRoot` used to publish through
|
|
16
16
|
* `CartDataContext`. A handler reads the current cart from the normalized Apollo cache (keyed by
|
|
@@ -48,7 +48,7 @@ function publishCoupon(errors) {
|
|
|
48
48
|
/**
|
|
49
49
|
* Publish a write's outcome and, when it came back clean, stamp the availability freshness window —
|
|
50
50
|
* a clean write's payload carries the platform's recomputed cart, which is what lets the
|
|
51
|
-
* validate-on-open call skip (
|
|
51
|
+
* validate-on-open call skip (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Used only on paths
|
|
52
52
|
* where a mutation actually reached the server: a `userErrors` response carries `cart: null` and a
|
|
53
53
|
* no-op (unknown line) issues no request, so neither refreshes availability.
|
|
54
54
|
*/
|
|
@@ -75,7 +75,7 @@ function hydrateCartId(cartId) {
|
|
|
75
75
|
* null before the cart is minted/loaded. Module-level so it can be shared with the read-only callers
|
|
76
76
|
* that live outside this hook — the pre-checkout gate re-reads the corrected cart this way after
|
|
77
77
|
* `validate` resolves, because Apollo commits the mutation's cache write before the promise settles
|
|
78
|
-
* (
|
|
78
|
+
* (docs/storefront-core/reference-cart.md § Validation). The codegen `CartFragment` document embeds its
|
|
79
79
|
* nested fragments, so it is a self-contained read.
|
|
80
80
|
*/
|
|
81
81
|
function readCartFromCache(client) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-cart-actions.js","names":[],"sources":["../../../src/sdk/cart/use-cart-actions.ts"],"sourcesContent":["'use client';\n\nimport type { ApolloClient } from '@apollo/client';\nimport { useApolloClient, useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport { cartCouponErrorsVar, cartLastAddVar, cartUserErrorsVar, orderFormIdVar } from '../state/cart.js';\nimport {\n CART_COUPON_APPLY,\n CART_COUPON_REMOVE,\n CART_ITEM_ADD,\n CART_ITEM_QUANTITY_DECREASE,\n CART_ITEM_QUANTITY_INCREASE,\n CART_ITEM_REMOVE,\n CART_ITEMS_REMOVE,\n CART_VALIDATE,\n type CartEntity,\n CartFragment,\n type CartItemEntity,\n type CartUserError,\n} from './operations.js';\nimport {\n buildOptimisticDecrease,\n buildOptimisticIncrease,\n buildOptimisticRemove,\n buildOptimisticRemoveItems,\n} from './optimistic.js';\nimport { markAvailabilityFresh, trackCartWrite } from './write-gate.js';\n\n/**\n * The `cart` action namespace — the consolidated, context-free owner of every cart write\n * (specs/actions.md, docs/storefront-core/adr-0010-actions-own-the-write-side.md).\n *\n * It replaces `useCartMutations` + the mutation handlers `CartRoot` used to publish through\n * `CartDataContext`. A handler reads the current cart from the normalized Apollo cache (keyed by\n * `orderFormIdVar`), not from component props or React context, so any component anywhere in the\n * tree can dispatch a write through the compiler-injected `$actions.cart.*` prop — the cache is the\n * only meeting point between the read side and the write side (ADR-0010).\n *\n * `userErrors` from every call are published to `cartUserErrorsVar` (cleared at the start of the\n * next call) so `CartError` can surface them without a data context, and also returned to the caller.\n */\n\n/**\n * Thrown by the existing-line handlers (remove / quantity) when invoked before the cart resolves.\n * The first `addItem` mints the cart and hydrates `orderFormIdVar`; until then there is no cached\n * cart entity to read for the optimistic response.\n */\nexport class CartNotReadyError extends Error {\n constructor(message?: string) {\n super(message ?? 'CartNotReadyError: no cart in cache yet; add an item (or load the cart) before mutating a line.');\n this.name = 'CartNotReadyError';\n }\n}\n\nexport type AddItemPayload = Readonly<{\n variantId: string;\n quantity?: number;\n /** Seller global Node ID, e.g. base64(\"Seller:1\"). The server rejects raw ids with INVALID_SELLER_ID. */\n seller: string;\n}>;\n\n/** Existing-line payloads address the line by its opaque cart-item `id`; the handler reads the rest from cache. */\nexport type CartLinePayload = Readonly<{ id: string }>;\n\n/** Batch payloads address several lines by their opaque cart-item `id`s. */\nexport type CartLinesPayload = Readonly<{ ids: readonly string[] }>;\n\nexport type CartQuantityPayload = Readonly<{ id: string; by?: number }>;\n\n/** Coupon writes address the coupon by its `code`; the server resolves the cart from the cookie. */\nexport type CartCouponPayload = Readonly<{ code: string }>;\n\n/**\n * The `cart` namespace surface. Item writes return the resulting `userErrors` (empty on success)\n * and publish them to `cartUserErrorsVar`; coupon writes publish to the dedicated\n * `cartCouponErrorsVar` (specs/fspg-830-coupon-entry.md AD-2).\n */\nexport type CartActions = Readonly<{\n /** Add an item to the cart. Async (no optimistic response). Creates the cart on first add. */\n addItem: (payload: AddItemPayload) => Promise<readonly CartUserError[]>;\n /** Remove a line entirely. Optimistic. */\n removeItem: (payload: CartLinePayload) => Promise<readonly CartUserError[]>;\n /** Remove several lines in one mutation. Optimistic. Ids missing from the cache are skipped. */\n removeItems: (payload: CartLinesPayload) => Promise<readonly CartUserError[]>;\n /** Increase a line's quantity by N (default 1). Optimistic. */\n increaseQuantity: (payload: CartQuantityPayload) => Promise<readonly CartUserError[]>;\n /** Decrease a line's quantity by N (default 1). Optimistic. Removes the line when result <= 0. */\n decreaseQuantity: (payload: CartQuantityPayload) => Promise<readonly CartUserError[]>;\n /** Apply a coupon code. Async (no optimistic). Publishes userErrors to cartCouponErrorsVar. */\n applyCoupon: (payload: CartCouponPayload) => Promise<readonly CartUserError[]>;\n /** Remove a coupon code by code. Async (no optimistic). Publishes userErrors to cartCouponErrorsVar. */\n removeCoupon: (payload: CartCouponPayload) => Promise<readonly CartUserError[]>;\n /**\n * Re-check the active cart against the platform and adopt corrected availability. Async (no\n * optimistic), read-only upstream. Returns userErrors (empty on success) to its caller and stamps\n * the freshness window on success. Unlike the item writes it does NOT publish to\n * `cartUserErrorsVar` — a speculative open-time failure must raise no error banner (Decision 6).\n */\n validate: () => Promise<readonly CartUserError[]>;\n}>;\n\nconst NETWORK_USER_ERROR: CartUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to update cart. Please try again.',\n};\n\nfunction publish(errors: readonly CartUserError[]): readonly CartUserError[] {\n cartUserErrorsVar(errors);\n return errors;\n}\n\n/** Coupon-scoped publish — keeps coupon feedback off the shared item-error channel (AD-2). */\nfunction publishCoupon(errors: readonly CartUserError[]): readonly CartUserError[] {\n cartCouponErrorsVar(errors);\n return errors;\n}\n\n/**\n * Publish a write's outcome and, when it came back clean, stamp the availability freshness window —\n * a clean write's payload carries the platform's recomputed cart, which is what lets the\n * validate-on-open call skip (specs/mini-cart-validation-wiring.md, Decision 2). Used only on paths\n * where a mutation actually reached the server: a `userErrors` response carries `cart: null` and a\n * no-op (unknown line) issues no request, so neither refreshes availability.\n */\nfunction publishWrite(errors: readonly CartUserError[]): readonly CartUserError[] {\n if (errors.length === 0) markAvailabilityFresh();\n return publish(errors);\n}\n\n/** Coupon-channel counterpart of `publishWrite`. */\nfunction publishCouponWrite(errors: readonly CartUserError[]): readonly CartUserError[] {\n if (errors.length === 0) markAvailabilityFresh();\n return publishCoupon(errors);\n}\n\n/**\n * Backfill `orderFormIdVar` from a mutation's returned `cart.id` when local state is still cold.\n * A coupon op can be the first cart write (SSR cold / drawer closed on first load); without this the\n * later `requireLine` paths short-circuit on `orderFormIdVar() === null` and throw `CartNotReadyError`.\n * Mirrors the `addItem` first-add handoff.\n */\nfunction hydrateCartId(cartId: string | null): void {\n if (cartId && orderFormIdVar() === null) orderFormIdVar(cartId);\n}\n\n/**\n * Read the live cart entity from the normalized cache (ADR-0010), keyed by `orderFormIdVar`. Returns\n * null before the cart is minted/loaded. Module-level so it can be shared with the read-only callers\n * that live outside this hook — the pre-checkout gate re-reads the corrected cart this way after\n * `validate` resolves, because Apollo commits the mutation's cache write before the promise settles\n * (specs/mini-cart-validation-wiring.md, Decision 3). The codegen `CartFragment` document embeds its\n * nested fragments, so it is a self-contained read.\n */\nexport function readCartFromCache(client: ApolloClient): CartEntity | null {\n const cartId = orderFormIdVar();\n if (!cartId) return null;\n const cacheId = client.cache.identify({ __typename: 'Cart', id: cartId });\n if (!cacheId) return null;\n return (\n client.cache.readFragment<CartEntity>({ id: cacheId, fragment: CartFragment, fragmentName: 'CartFragment' }) ?? null\n );\n}\n\n/**\n * The `cart` namespace hook. Registered with the first-party action registry via `cartActions`\n * below; the compiler-emitted `CartActionProvider` calls it and threads the narrowed methods into\n * each consumer's `$actions.cart`.\n */\nexport function useCartActions(): CartActions {\n const client = useApolloClient();\n\n const [addMutation] = useMutation(CART_ITEM_ADD);\n const [removeMutation] = useMutation(CART_ITEM_REMOVE);\n const [removeItemsMutation] = useMutation(CART_ITEMS_REMOVE);\n const [increaseMutation] = useMutation(CART_ITEM_QUANTITY_INCREASE);\n const [decreaseMutation] = useMutation(CART_ITEM_QUANTITY_DECREASE);\n const [applyCouponMutation] = useMutation(CART_COUPON_APPLY);\n const [removeCouponMutation] = useMutation(CART_COUPON_REMOVE);\n const [validateMutation] = useMutation(CART_VALIDATE);\n\n const readCart = useCallback((): CartEntity | null => readCartFromCache(client), [client]);\n\n const requireLine = useCallback(\n (id: string): { cart: CartEntity; line: CartItemEntity } | null => {\n const cart = readCart();\n if (!cart) throw new CartNotReadyError();\n const line = cart.items.find((item) => item.id === id);\n if (!line) return null;\n return { cart, line };\n },\n [readCart],\n );\n\n const addItem = useCallback<CartActions['addItem']>(\n async (payload) => {\n cartUserErrorsVar([]);\n try {\n const result = await trackCartWrite(\n addMutation({\n variables: {\n input: { variantId: payload.variantId, quantity: payload.quantity ?? 1, seller: payload.seller },\n },\n }),\n );\n // Very-first add: the server minted a cart from the empty transport context. Hydrate the\n // var so the existing-line handlers unlock without requiring a cart query.\n if (orderFormIdVar() === null) {\n const newId = result.data?.cartItemAdd?.cart?.id ?? null;\n if (newId) orderFormIdVar(newId);\n }\n const errors = result.data?.cartItemAdd?.userErrors ?? [];\n // Bump the add-success pulse only on the clean branch; the post-add behavior components\n // (CartOpenOnAdd / CartRedirectOnAdd) key their effect on it. The userErrors and\n // network-catch branches leave it untouched (specs/fspg-822-minicart-open-on-add.md).\n if (errors.length === 0) cartLastAddVar(cartLastAddVar() + 1);\n return publishWrite(errors);\n } catch (error) {\n console.error('[cart.addItem] cartItemAdd failed', error);\n return publish([NETWORK_USER_ERROR]);\n }\n },\n [addMutation],\n );\n\n const removeItem = useCallback<CartActions['removeItem']>(\n async (payload) => {\n const found = requireLine(payload.id);\n if (!found) return publish([]);\n cartUserErrorsVar([]);\n try {\n const result = await trackCartWrite(\n removeMutation({\n variables: { input: { variantId: found.line.variant.variantId } },\n optimisticResponse: buildOptimisticRemove(found.cart, found.line),\n }),\n );\n return publishWrite(result.data?.cartItemRemove?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.removeItem] cartItemRemove failed', error);\n return publish([NETWORK_USER_ERROR]);\n }\n },\n [requireLine, removeMutation],\n );\n\n const removeItems = useCallback<CartActions['removeItems']>(\n async (payload) => {\n const cart = readCart();\n if (!cart) throw new CartNotReadyError();\n // Client-side tolerance mirroring removeItem's not-found no-op: ids without a cached line\n // are skipped, and the surviving set is deduped before the single mutation.\n const lines = cart.items.filter((item) => payload.ids.includes(item.id));\n if (lines.length === 0) return publish([]);\n const variantIds = [...new Set(lines.map((line) => line.variant.variantId))];\n cartUserErrorsVar([]);\n try {\n const result = await trackCartWrite(\n removeItemsMutation({\n variables: { input: { variantIds } },\n optimisticResponse: buildOptimisticRemoveItems(cart, lines),\n }),\n );\n return publishWrite(result.data?.cartItemsRemove?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.removeItems] cartItemsRemove failed', error);\n return publish([NETWORK_USER_ERROR]);\n }\n },\n [readCart, removeItemsMutation],\n );\n\n const increaseQuantity = useCallback<CartActions['increaseQuantity']>(\n async (payload) => {\n const by = payload.by ?? 1;\n const found = requireLine(payload.id);\n if (!found) return publish([]);\n cartUserErrorsVar([]);\n try {\n const result = await trackCartWrite(\n increaseMutation({\n variables: { input: { variantId: found.line.variant.variantId, quantityToAdd: by } },\n optimisticResponse: buildOptimisticIncrease(found.cart, found.line, by),\n }),\n );\n return publishWrite(result.data?.cartItemQuantityIncrease?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.increaseQuantity] cartItemQuantityIncrease failed', error);\n return publish([NETWORK_USER_ERROR]);\n }\n },\n [requireLine, increaseMutation],\n );\n\n const decreaseQuantity = useCallback<CartActions['decreaseQuantity']>(\n async (payload) => {\n const by = payload.by ?? 1;\n const found = requireLine(payload.id);\n if (!found) return publish([]);\n cartUserErrorsVar([]);\n try {\n const result = await trackCartWrite(\n decreaseMutation({\n variables: { input: { variantId: found.line.variant.variantId, quantityToRemove: by } },\n optimisticResponse: buildOptimisticDecrease(found.cart, found.line, by),\n }),\n );\n return publishWrite(result.data?.cartItemQuantityDecrease?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.decreaseQuantity] cartItemQuantityDecrease failed', error);\n return publish([NETWORK_USER_ERROR]);\n }\n },\n [requireLine, decreaseMutation],\n );\n\n // Coupons are cart-level: no `requireLine` gate (AD-9). No optimistic response and no client\n // rollback — totals and `isUnmatchedCondition` are server-computed and the server rolls back its\n // own single-coupon rejections; the returned cart (spreading the extended CartFragment)\n // self-updates the cache and every reactive subscriber (specs/fspg-830-coupon-entry.md AD-3/AD-8).\n const applyCoupon = useCallback<CartActions['applyCoupon']>(\n async (payload) => {\n cartCouponErrorsVar([]);\n try {\n const result = await trackCartWrite(applyCouponMutation({ variables: { input: { code: payload.code } } }));\n hydrateCartId(result.data?.cartCouponApply?.cart?.id ?? null);\n return publishCouponWrite(result.data?.cartCouponApply?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.applyCoupon] cartCouponApply failed', error);\n return publishCoupon([NETWORK_USER_ERROR]);\n }\n },\n [applyCouponMutation],\n );\n\n const removeCoupon = useCallback<CartActions['removeCoupon']>(\n async (payload) => {\n cartCouponErrorsVar([]);\n try {\n const result = await trackCartWrite(removeCouponMutation({ variables: { input: { code: payload.code } } }));\n hydrateCartId(result.data?.cartCouponRemove?.cart?.id ?? null);\n return publishCouponWrite(result.data?.cartCouponRemove?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.removeCoupon] cartCouponRemove failed', error);\n return publishCoupon([NETWORK_USER_ERROR]);\n }\n },\n [removeCouponMutation],\n );\n\n // Read-only upstream (specs/mini-cart-validate-operation.md): re-simulates the cart and returns it\n // with `variant.availability` corrected, spreading CartFragment so the guard, the per-line alert,\n // and the badge re-render off the cache. Deliberately unlike the item writes: it does NOT register\n // as an in-flight write (it would gate itself) and it does NOT publish to cartUserErrorsVar — the\n // open call is speculative and the pre-checkout call is fail-open, so a failure must raise no error\n // banner (Decision 6). It stamps the freshness window on success so open-on-add does not re-check.\n const validate = useCallback<CartActions['validate']>(async () => {\n try {\n const result = await validateMutation();\n // Hydrate as the coupon writes do: the corrected cart is now in the cache, and the pre-checkout\n // gate finds it by `orderFormIdVar`. Without this a cold var would leave the gate reading null\n // and passing a cart it never actually checked.\n hydrateCartId(result.data?.cartValidate?.cart?.id ?? null);\n const errors = result.data?.cartValidate?.userErrors ?? [];\n if (errors.length === 0) markAvailabilityFresh();\n return errors;\n } catch (error) {\n console.error('[cart.validate] cartValidate failed', error);\n return [NETWORK_USER_ERROR];\n }\n }, [validateMutation]);\n\n return useMemo(\n () => ({\n addItem,\n removeItem,\n removeItems,\n increaseQuantity,\n decreaseQuantity,\n applyCoupon,\n removeCoupon,\n validate,\n }),\n [addItem, removeItem, removeItems, increaseQuantity, decreaseQuantity, applyCoupon, removeCoupon, validate],\n );\n}\n\n/**\n * Declares the `cart` action namespace — the single source of truth for its method/payload metadata,\n * runtime provider, and `use` hook (mirrors `defineComponent`). The native catalog builder\n * (`@vtex/faststore-catalog`'s `buildPackageCatalog`) statically reads this call to emit `actions.json`;\n * at runtime `CartActionProvider` calls `useCartActions` directly.\n */\nexport const cartActions = defineActions({\n namespace: 'cart',\n description: 'Mutation-backed writes against the current cart (consolidates useCartMutations / CartRoot).',\n provider: { name: 'CartActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n addItem: {\n description: 'Add an item to the cart (async strategy). Creates the cart on first add.',\n payload: [\n { name: 'variantId', type: 'string' },\n { name: 'seller', type: 'string' },\n { name: 'quantity', type: 'number', optional: true },\n ],\n },\n removeItem: {\n description: 'Remove a line from the cart (optimistic strategy).',\n payload: [{ name: 'id', type: 'string' }],\n },\n removeItems: {\n description: 'Remove several lines from the cart in one call (optimistic strategy).',\n payload: [{ name: 'ids', type: 'string[]' }],\n },\n increaseQuantity: {\n description: \"Increase a line's quantity by N (default 1, optimistic strategy).\",\n payload: [\n { name: 'id', type: 'string' },\n { name: 'by', type: 'number', optional: true },\n ],\n },\n decreaseQuantity: {\n description: \"Decrease a line's quantity by N (default 1, optimistic). Removes the line at <= 0.\",\n payload: [\n { name: 'id', type: 'string' },\n { name: 'by', type: 'number', optional: true },\n ],\n },\n applyCoupon: {\n description: 'Apply a coupon code to the cart (async strategy). Server recomputes totals.',\n payload: [{ name: 'code', type: 'string' }],\n },\n removeCoupon: {\n description: 'Remove a coupon code from the cart (async strategy). Server recomputes totals.',\n payload: [{ name: 'code', type: 'string' }],\n },\n validate: {\n description:\n 'Re-check the active cart against the platform and adopt corrected availability (async, read-only upstream).',\n payload: [],\n },\n },\n use: useCartActions,\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-cart-actions.js","names":[],"sources":["../../../src/sdk/cart/use-cart-actions.ts"],"sourcesContent":["'use client';\n\nimport type { ApolloClient } from '@apollo/client';\nimport { useApolloClient, useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport { cartCouponErrorsVar, cartLastAddVar, cartUserErrorsVar, orderFormIdVar } from '../state/cart.js';\nimport {\n CART_COUPON_APPLY,\n CART_COUPON_REMOVE,\n CART_ITEM_ADD,\n CART_ITEM_QUANTITY_DECREASE,\n CART_ITEM_QUANTITY_INCREASE,\n CART_ITEM_REMOVE,\n CART_ITEMS_REMOVE,\n CART_VALIDATE,\n type CartEntity,\n CartFragment,\n type CartItemEntity,\n type CartUserError,\n} from './operations.js';\nimport {\n buildOptimisticDecrease,\n buildOptimisticIncrease,\n buildOptimisticRemove,\n buildOptimisticRemoveItems,\n} from './optimistic.js';\nimport { markAvailabilityFresh, trackCartWrite } from './write-gate.js';\n\n/**\n * The `cart` action namespace — the consolidated, context-free owner of every cart write\n * (docs/storefront-core/reference-free-particle-model.md § The cart write contract, docs/storefront-core/adr-0010-actions-own-the-write-side.md).\n *\n * It replaces `useCartMutations` + the mutation handlers `CartRoot` used to publish through\n * `CartDataContext`. A handler reads the current cart from the normalized Apollo cache (keyed by\n * `orderFormIdVar`), not from component props or React context, so any component anywhere in the\n * tree can dispatch a write through the compiler-injected `$actions.cart.*` prop — the cache is the\n * only meeting point between the read side and the write side (ADR-0010).\n *\n * `userErrors` from every call are published to `cartUserErrorsVar` (cleared at the start of the\n * next call) so `CartError` can surface them without a data context, and also returned to the caller.\n */\n\n/**\n * Thrown by the existing-line handlers (remove / quantity) when invoked before the cart resolves.\n * The first `addItem` mints the cart and hydrates `orderFormIdVar`; until then there is no cached\n * cart entity to read for the optimistic response.\n */\nexport class CartNotReadyError extends Error {\n constructor(message?: string) {\n super(message ?? 'CartNotReadyError: no cart in cache yet; add an item (or load the cart) before mutating a line.');\n this.name = 'CartNotReadyError';\n }\n}\n\nexport type AddItemPayload = Readonly<{\n variantId: string;\n quantity?: number;\n /** Seller global Node ID, e.g. base64(\"Seller:1\"). The server rejects raw ids with INVALID_SELLER_ID. */\n seller: string;\n}>;\n\n/** Existing-line payloads address the line by its opaque cart-item `id`; the handler reads the rest from cache. */\nexport type CartLinePayload = Readonly<{ id: string }>;\n\n/** Batch payloads address several lines by their opaque cart-item `id`s. */\nexport type CartLinesPayload = Readonly<{ ids: readonly string[] }>;\n\nexport type CartQuantityPayload = Readonly<{ id: string; by?: number }>;\n\n/** Coupon writes address the coupon by its `code`; the server resolves the cart from the cookie. */\nexport type CartCouponPayload = Readonly<{ code: string }>;\n\n/**\n * The `cart` namespace surface. Item writes return the resulting `userErrors` (empty on success)\n * and publish them to `cartUserErrorsVar`; coupon writes publish to the dedicated\n * `cartCouponErrorsVar` (docs/storefront-core/reference-free-particle-model.md § The cart write contract).\n */\nexport type CartActions = Readonly<{\n /** Add an item to the cart. Async (no optimistic response). Creates the cart on first add. */\n addItem: (payload: AddItemPayload) => Promise<readonly CartUserError[]>;\n /** Remove a line entirely. Optimistic. */\n removeItem: (payload: CartLinePayload) => Promise<readonly CartUserError[]>;\n /** Remove several lines in one mutation. Optimistic. Ids missing from the cache are skipped. */\n removeItems: (payload: CartLinesPayload) => Promise<readonly CartUserError[]>;\n /** Increase a line's quantity by N (default 1). Optimistic. */\n increaseQuantity: (payload: CartQuantityPayload) => Promise<readonly CartUserError[]>;\n /** Decrease a line's quantity by N (default 1). Optimistic. Removes the line when result <= 0. */\n decreaseQuantity: (payload: CartQuantityPayload) => Promise<readonly CartUserError[]>;\n /** Apply a coupon code. Async (no optimistic). Publishes userErrors to cartCouponErrorsVar. */\n applyCoupon: (payload: CartCouponPayload) => Promise<readonly CartUserError[]>;\n /** Remove a coupon code by code. Async (no optimistic). Publishes userErrors to cartCouponErrorsVar. */\n removeCoupon: (payload: CartCouponPayload) => Promise<readonly CartUserError[]>;\n /**\n * Re-check the active cart against the platform and adopt corrected availability. Async (no\n * optimistic), read-only upstream. Returns userErrors (empty on success) to its caller and stamps\n * the freshness window on success. Unlike the item writes it does NOT publish to\n * `cartUserErrorsVar` — a speculative open-time failure must raise no error banner (Decision 6).\n */\n validate: () => Promise<readonly CartUserError[]>;\n}>;\n\nconst NETWORK_USER_ERROR: CartUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to update cart. Please try again.',\n};\n\nfunction publish(errors: readonly CartUserError[]): readonly CartUserError[] {\n cartUserErrorsVar(errors);\n return errors;\n}\n\n/** Coupon-scoped publish — keeps coupon feedback off the shared item-error channel (AD-2). */\nfunction publishCoupon(errors: readonly CartUserError[]): readonly CartUserError[] {\n cartCouponErrorsVar(errors);\n return errors;\n}\n\n/**\n * Publish a write's outcome and, when it came back clean, stamp the availability freshness window —\n * a clean write's payload carries the platform's recomputed cart, which is what lets the\n * validate-on-open call skip (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Used only on paths\n * where a mutation actually reached the server: a `userErrors` response carries `cart: null` and a\n * no-op (unknown line) issues no request, so neither refreshes availability.\n */\nfunction publishWrite(errors: readonly CartUserError[]): readonly CartUserError[] {\n if (errors.length === 0) markAvailabilityFresh();\n return publish(errors);\n}\n\n/** Coupon-channel counterpart of `publishWrite`. */\nfunction publishCouponWrite(errors: readonly CartUserError[]): readonly CartUserError[] {\n if (errors.length === 0) markAvailabilityFresh();\n return publishCoupon(errors);\n}\n\n/**\n * Backfill `orderFormIdVar` from a mutation's returned `cart.id` when local state is still cold.\n * A coupon op can be the first cart write (SSR cold / drawer closed on first load); without this the\n * later `requireLine` paths short-circuit on `orderFormIdVar() === null` and throw `CartNotReadyError`.\n * Mirrors the `addItem` first-add handoff.\n */\nfunction hydrateCartId(cartId: string | null): void {\n if (cartId && orderFormIdVar() === null) orderFormIdVar(cartId);\n}\n\n/**\n * Read the live cart entity from the normalized cache (ADR-0010), keyed by `orderFormIdVar`. Returns\n * null before the cart is minted/loaded. Module-level so it can be shared with the read-only callers\n * that live outside this hook — the pre-checkout gate re-reads the corrected cart this way after\n * `validate` resolves, because Apollo commits the mutation's cache write before the promise settles\n * (docs/storefront-core/reference-cart.md § Validation). The codegen `CartFragment` document embeds its\n * nested fragments, so it is a self-contained read.\n */\nexport function readCartFromCache(client: ApolloClient): CartEntity | null {\n const cartId = orderFormIdVar();\n if (!cartId) return null;\n const cacheId = client.cache.identify({ __typename: 'Cart', id: cartId });\n if (!cacheId) return null;\n return (\n client.cache.readFragment<CartEntity>({ id: cacheId, fragment: CartFragment, fragmentName: 'CartFragment' }) ?? null\n );\n}\n\n/**\n * The `cart` namespace hook. Registered with the first-party action registry via `cartActions`\n * below; the compiler-emitted `CartActionProvider` calls it and threads the narrowed methods into\n * each consumer's `$actions.cart`.\n */\nexport function useCartActions(): CartActions {\n const client = useApolloClient();\n\n const [addMutation] = useMutation(CART_ITEM_ADD);\n const [removeMutation] = useMutation(CART_ITEM_REMOVE);\n const [removeItemsMutation] = useMutation(CART_ITEMS_REMOVE);\n const [increaseMutation] = useMutation(CART_ITEM_QUANTITY_INCREASE);\n const [decreaseMutation] = useMutation(CART_ITEM_QUANTITY_DECREASE);\n const [applyCouponMutation] = useMutation(CART_COUPON_APPLY);\n const [removeCouponMutation] = useMutation(CART_COUPON_REMOVE);\n const [validateMutation] = useMutation(CART_VALIDATE);\n\n const readCart = useCallback((): CartEntity | null => readCartFromCache(client), [client]);\n\n const requireLine = useCallback(\n (id: string): { cart: CartEntity; line: CartItemEntity } | null => {\n const cart = readCart();\n if (!cart) throw new CartNotReadyError();\n const line = cart.items.find((item) => item.id === id);\n if (!line) return null;\n return { cart, line };\n },\n [readCart],\n );\n\n const addItem = useCallback<CartActions['addItem']>(\n async (payload) => {\n cartUserErrorsVar([]);\n try {\n const result = await trackCartWrite(\n addMutation({\n variables: {\n input: { variantId: payload.variantId, quantity: payload.quantity ?? 1, seller: payload.seller },\n },\n }),\n );\n // Very-first add: the server minted a cart from the empty transport context. Hydrate the\n // var so the existing-line handlers unlock without requiring a cart query.\n if (orderFormIdVar() === null) {\n const newId = result.data?.cartItemAdd?.cart?.id ?? null;\n if (newId) orderFormIdVar(newId);\n }\n const errors = result.data?.cartItemAdd?.userErrors ?? [];\n // Bump the add-success pulse only on the clean branch; the post-add behavior components\n // (CartOpenOnAdd / CartRedirectOnAdd) key their effect on it. The userErrors and\n // network-catch branches leave it untouched (docs/storefront-core/reference-free-particle-model.md § The cart write contract).\n if (errors.length === 0) cartLastAddVar(cartLastAddVar() + 1);\n return publishWrite(errors);\n } catch (error) {\n console.error('[cart.addItem] cartItemAdd failed', error);\n return publish([NETWORK_USER_ERROR]);\n }\n },\n [addMutation],\n );\n\n const removeItem = useCallback<CartActions['removeItem']>(\n async (payload) => {\n const found = requireLine(payload.id);\n if (!found) return publish([]);\n cartUserErrorsVar([]);\n try {\n const result = await trackCartWrite(\n removeMutation({\n variables: { input: { variantId: found.line.variant.variantId } },\n optimisticResponse: buildOptimisticRemove(found.cart, found.line),\n }),\n );\n return publishWrite(result.data?.cartItemRemove?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.removeItem] cartItemRemove failed', error);\n return publish([NETWORK_USER_ERROR]);\n }\n },\n [requireLine, removeMutation],\n );\n\n const removeItems = useCallback<CartActions['removeItems']>(\n async (payload) => {\n const cart = readCart();\n if (!cart) throw new CartNotReadyError();\n // Client-side tolerance mirroring removeItem's not-found no-op: ids without a cached line\n // are skipped, and the surviving set is deduped before the single mutation.\n const lines = cart.items.filter((item) => payload.ids.includes(item.id));\n if (lines.length === 0) return publish([]);\n const variantIds = [...new Set(lines.map((line) => line.variant.variantId))];\n cartUserErrorsVar([]);\n try {\n const result = await trackCartWrite(\n removeItemsMutation({\n variables: { input: { variantIds } },\n optimisticResponse: buildOptimisticRemoveItems(cart, lines),\n }),\n );\n return publishWrite(result.data?.cartItemsRemove?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.removeItems] cartItemsRemove failed', error);\n return publish([NETWORK_USER_ERROR]);\n }\n },\n [readCart, removeItemsMutation],\n );\n\n const increaseQuantity = useCallback<CartActions['increaseQuantity']>(\n async (payload) => {\n const by = payload.by ?? 1;\n const found = requireLine(payload.id);\n if (!found) return publish([]);\n cartUserErrorsVar([]);\n try {\n const result = await trackCartWrite(\n increaseMutation({\n variables: { input: { variantId: found.line.variant.variantId, quantityToAdd: by } },\n optimisticResponse: buildOptimisticIncrease(found.cart, found.line, by),\n }),\n );\n return publishWrite(result.data?.cartItemQuantityIncrease?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.increaseQuantity] cartItemQuantityIncrease failed', error);\n return publish([NETWORK_USER_ERROR]);\n }\n },\n [requireLine, increaseMutation],\n );\n\n const decreaseQuantity = useCallback<CartActions['decreaseQuantity']>(\n async (payload) => {\n const by = payload.by ?? 1;\n const found = requireLine(payload.id);\n if (!found) return publish([]);\n cartUserErrorsVar([]);\n try {\n const result = await trackCartWrite(\n decreaseMutation({\n variables: { input: { variantId: found.line.variant.variantId, quantityToRemove: by } },\n optimisticResponse: buildOptimisticDecrease(found.cart, found.line, by),\n }),\n );\n return publishWrite(result.data?.cartItemQuantityDecrease?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.decreaseQuantity] cartItemQuantityDecrease failed', error);\n return publish([NETWORK_USER_ERROR]);\n }\n },\n [requireLine, decreaseMutation],\n );\n\n // Coupons are cart-level: no `requireLine` gate (AD-9). No optimistic response and no client\n // rollback — totals and `isUnmatchedCondition` are server-computed and the server rolls back its\n // own single-coupon rejections; the returned cart (spreading the extended CartFragment)\n // self-updates the cache and every reactive subscriber (docs/storefront-core/reference-free-particle-model.md § The cart write contract).\n const applyCoupon = useCallback<CartActions['applyCoupon']>(\n async (payload) => {\n cartCouponErrorsVar([]);\n try {\n const result = await trackCartWrite(applyCouponMutation({ variables: { input: { code: payload.code } } }));\n hydrateCartId(result.data?.cartCouponApply?.cart?.id ?? null);\n return publishCouponWrite(result.data?.cartCouponApply?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.applyCoupon] cartCouponApply failed', error);\n return publishCoupon([NETWORK_USER_ERROR]);\n }\n },\n [applyCouponMutation],\n );\n\n const removeCoupon = useCallback<CartActions['removeCoupon']>(\n async (payload) => {\n cartCouponErrorsVar([]);\n try {\n const result = await trackCartWrite(removeCouponMutation({ variables: { input: { code: payload.code } } }));\n hydrateCartId(result.data?.cartCouponRemove?.cart?.id ?? null);\n return publishCouponWrite(result.data?.cartCouponRemove?.userErrors ?? []);\n } catch (error) {\n console.error('[cart.removeCoupon] cartCouponRemove failed', error);\n return publishCoupon([NETWORK_USER_ERROR]);\n }\n },\n [removeCouponMutation],\n );\n\n // Read-only upstream (docs/storefront-core/reference-free-particle-model.md § The cart write contract): re-simulates the cart and returns it\n // with `variant.availability` corrected, spreading CartFragment so the guard, the per-line alert,\n // and the badge re-render off the cache. Deliberately unlike the item writes: it does NOT register\n // as an in-flight write (it would gate itself) and it does NOT publish to cartUserErrorsVar — the\n // open call is speculative and the pre-checkout call is fail-open, so a failure must raise no error\n // banner (Decision 6). It stamps the freshness window on success so open-on-add does not re-check.\n const validate = useCallback<CartActions['validate']>(async () => {\n try {\n const result = await validateMutation();\n // Hydrate as the coupon writes do: the corrected cart is now in the cache, and the pre-checkout\n // gate finds it by `orderFormIdVar`. Without this a cold var would leave the gate reading null\n // and passing a cart it never actually checked.\n hydrateCartId(result.data?.cartValidate?.cart?.id ?? null);\n const errors = result.data?.cartValidate?.userErrors ?? [];\n if (errors.length === 0) markAvailabilityFresh();\n return errors;\n } catch (error) {\n console.error('[cart.validate] cartValidate failed', error);\n return [NETWORK_USER_ERROR];\n }\n }, [validateMutation]);\n\n return useMemo(\n () => ({\n addItem,\n removeItem,\n removeItems,\n increaseQuantity,\n decreaseQuantity,\n applyCoupon,\n removeCoupon,\n validate,\n }),\n [addItem, removeItem, removeItems, increaseQuantity, decreaseQuantity, applyCoupon, removeCoupon, validate],\n );\n}\n\n/**\n * Declares the `cart` action namespace — the single source of truth for its method/payload metadata,\n * runtime provider, and `use` hook (mirrors `defineComponent`). The native catalog builder\n * (`@vtex/faststore-catalog`'s `buildPackageCatalog`) statically reads this call to emit `actions.json`;\n * at runtime `CartActionProvider` calls `useCartActions` directly.\n */\nexport const cartActions = defineActions({\n namespace: 'cart',\n description: 'Mutation-backed writes against the current cart (consolidates useCartMutations / CartRoot).',\n provider: { name: 'CartActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n addItem: {\n description: 'Add an item to the cart (async strategy). Creates the cart on first add.',\n payload: [\n { name: 'variantId', type: 'string' },\n { name: 'seller', type: 'string' },\n { name: 'quantity', type: 'number', optional: true },\n ],\n },\n removeItem: {\n description: 'Remove a line from the cart (optimistic strategy).',\n payload: [{ name: 'id', type: 'string' }],\n },\n removeItems: {\n description: 'Remove several lines from the cart in one call (optimistic strategy).',\n payload: [{ name: 'ids', type: 'string[]' }],\n },\n increaseQuantity: {\n description: \"Increase a line's quantity by N (default 1, optimistic strategy).\",\n payload: [\n { name: 'id', type: 'string' },\n { name: 'by', type: 'number', optional: true },\n ],\n },\n decreaseQuantity: {\n description: \"Decrease a line's quantity by N (default 1, optimistic). Removes the line at <= 0.\",\n payload: [\n { name: 'id', type: 'string' },\n { name: 'by', type: 'number', optional: true },\n ],\n },\n applyCoupon: {\n description: 'Apply a coupon code to the cart (async strategy). Server recomputes totals.',\n payload: [{ name: 'code', type: 'string' }],\n },\n removeCoupon: {\n description: 'Remove a coupon code from the cart (async strategy). Server recomputes totals.',\n payload: [{ name: 'code', type: 'string' }],\n },\n validate: {\n description:\n 'Re-check the active cart against the platform and adopt corrected availability (async, read-only upstream).',\n payload: [],\n },\n },\n use: useCartActions,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YAAY,SAAkB;EAC5B,MAAM,WAAW,iGAAiG;EAClH,KAAK,OAAO;CACd;AACF;AAiDA,MAAM,qBAAoC;CACxC,MAAM;CACN,SAAS;AACX;AAEA,SAAS,QAAQ,QAA4D;CAC3E,kBAAkB,MAAM;CACxB,OAAO;AACT;;AAGA,SAAS,cAAc,QAA4D;CACjF,oBAAoB,MAAM;CAC1B,OAAO;AACT;;;;;;;;AASA,SAAS,aAAa,QAA4D;CAChF,IAAI,OAAO,WAAW,GAAG,sBAAsB;CAC/C,OAAO,QAAQ,MAAM;AACvB;;AAGA,SAAS,mBAAmB,QAA4D;CACtF,IAAI,OAAO,WAAW,GAAG,sBAAsB;CAC/C,OAAO,cAAc,MAAM;AAC7B;;;;;;;AAQA,SAAS,cAAc,QAA6B;CAClD,IAAI,UAAU,eAAe,MAAM,MAAM,eAAe,MAAM;AAChE;;;;;;;;;AAUA,SAAgB,kBAAkB,QAAyC;CACzE,MAAM,SAAS,eAAe;CAC9B,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,UAAU,OAAO,MAAM,SAAS;EAAE,YAAY;EAAQ,IAAI;CAAO,CAAC;CACxE,IAAI,CAAC,SAAS,OAAO;CACrB,OACE,OAAO,MAAM,aAAyB;EAAE,IAAI;EAAS,UAAU;EAAc,cAAc;CAAe,CAAC,KAAK;AAEpH;;;;;;AAOA,SAAgB,iBAA8B;CAC5C,MAAM,SAAS,gBAAgB;CAE/B,MAAM,CAAC,eAAe,YAAY,aAAa;CAC/C,MAAM,CAAC,kBAAkB,YAAY,gBAAgB;CACrD,MAAM,CAAC,uBAAuB,YAAY,iBAAiB;CAC3D,MAAM,CAAC,oBAAoB,YAAY,2BAA2B;CAClE,MAAM,CAAC,oBAAoB,YAAY,2BAA2B;CAClE,MAAM,CAAC,uBAAuB,YAAY,iBAAiB;CAC3D,MAAM,CAAC,wBAAwB,YAAY,kBAAkB;CAC7D,MAAM,CAAC,oBAAoB,YAAY,aAAa;CAEpD,MAAM,WAAW,kBAAqC,kBAAkB,MAAM,GAAG,CAAC,MAAM,CAAC;CAEzF,MAAM,cAAc,aACjB,OAAkE;EACjE,MAAM,OAAO,SAAS;EACtB,IAAI,CAAC,MAAM,MAAM,IAAI,kBAAkB;EACvC,MAAM,OAAO,KAAK,MAAM,MAAM,SAAS,KAAK,OAAO,EAAE;EACrD,IAAI,CAAC,MAAM,OAAO;EAClB,OAAO;GAAE;GAAM;EAAK;CACtB,GACA,CAAC,QAAQ,CACX;CAEA,MAAM,UAAU,YACd,OAAO,YAAY;EACjB,kBAAkB,CAAC,CAAC;EACpB,IAAI;GACF,MAAM,SAAS,MAAM,eACnB,YAAY,EACV,WAAW,EACT,OAAO;IAAE,WAAW,QAAQ;IAAW,UAAU,QAAQ,YAAY;IAAG,QAAQ,QAAQ;GAAO,EACjG,EACF,CAAC,CACH;GAGA,IAAI,eAAe,MAAM,MAAM;IAC7B,MAAM,QAAQ,OAAO,MAAM,aAAa,MAAM,MAAM;IACpD,IAAI,OAAO,eAAe,KAAK;GACjC;GACA,MAAM,SAAS,OAAO,MAAM,aAAa,cAAc,CAAC;GAIxD,IAAI,OAAO,WAAW,GAAG,eAAe,eAAe,IAAI,CAAC;GAC5D,OAAO,aAAa,MAAM;EAC5B,SAAS,OAAO;GACd,QAAQ,MAAM,qCAAqC,KAAK;GACxD,OAAO,QAAQ,CAAC,kBAAkB,CAAC;EACrC;CACF,GACA,CAAC,WAAW,CACd;CAEA,MAAM,aAAa,YACjB,OAAO,YAAY;EACjB,MAAM,QAAQ,YAAY,QAAQ,EAAE;EACpC,IAAI,CAAC,OAAO,OAAO,QAAQ,CAAC,CAAC;EAC7B,kBAAkB,CAAC,CAAC;EACpB,IAAI;GAOF,OAAO,cAAa,MANC,eACnB,eAAe;IACb,WAAW,EAAE,OAAO,EAAE,WAAW,MAAM,KAAK,QAAQ,UAAU,EAAE;IAChE,oBAAoB,sBAAsB,MAAM,MAAM,MAAM,IAAI;GAClE,CAAC,CACH,EAAA,CAC2B,MAAM,gBAAgB,cAAc,CAAC,CAAC;EACnE,SAAS,OAAO;GACd,QAAQ,MAAM,2CAA2C,KAAK;GAC9D,OAAO,QAAQ,CAAC,kBAAkB,CAAC;EACrC;CACF,GACA,CAAC,aAAa,cAAc,CAC9B;CAEA,MAAM,cAAc,YAClB,OAAO,YAAY;EACjB,MAAM,OAAO,SAAS;EACtB,IAAI,CAAC,MAAM,MAAM,IAAI,kBAAkB;EAGvC,MAAM,QAAQ,KAAK,MAAM,QAAQ,SAAS,QAAQ,IAAI,SAAS,KAAK,EAAE,CAAC;EACvE,IAAI,MAAM,WAAW,GAAG,OAAO,QAAQ,CAAC,CAAC;EACzC,MAAM,aAAa,CAAC,GAAG,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,QAAQ,SAAS,CAAC,CAAC;EAC3E,kBAAkB,CAAC,CAAC;EACpB,IAAI;GAOF,OAAO,cAAa,MANC,eACnB,oBAAoB;IAClB,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE;IACnC,oBAAoB,2BAA2B,MAAM,KAAK;GAC5D,CAAC,CACH,EAAA,CAC2B,MAAM,iBAAiB,cAAc,CAAC,CAAC;EACpE,SAAS,OAAO;GACd,QAAQ,MAAM,6CAA6C,KAAK;GAChE,OAAO,QAAQ,CAAC,kBAAkB,CAAC;EACrC;CACF,GACA,CAAC,UAAU,mBAAmB,CAChC;CAEA,MAAM,mBAAmB,YACvB,OAAO,YAAY;EACjB,MAAM,KAAK,QAAQ,MAAM;EACzB,MAAM,QAAQ,YAAY,QAAQ,EAAE;EACpC,IAAI,CAAC,OAAO,OAAO,QAAQ,CAAC,CAAC;EAC7B,kBAAkB,CAAC,CAAC;EACpB,IAAI;GAOF,OAAO,cAAa,MANC,eACnB,iBAAiB;IACf,WAAW,EAAE,OAAO;KAAE,WAAW,MAAM,KAAK,QAAQ;KAAW,eAAe;IAAG,EAAE;IACnF,oBAAoB,wBAAwB,MAAM,MAAM,MAAM,MAAM,EAAE;GACxE,CAAC,CACH,EAAA,CAC2B,MAAM,0BAA0B,cAAc,CAAC,CAAC;EAC7E,SAAS,OAAO;GACd,QAAQ,MAAM,2DAA2D,KAAK;GAC9E,OAAO,QAAQ,CAAC,kBAAkB,CAAC;EACrC;CACF,GACA,CAAC,aAAa,gBAAgB,CAChC;CAEA,MAAM,mBAAmB,YACvB,OAAO,YAAY;EACjB,MAAM,KAAK,QAAQ,MAAM;EACzB,MAAM,QAAQ,YAAY,QAAQ,EAAE;EACpC,IAAI,CAAC,OAAO,OAAO,QAAQ,CAAC,CAAC;EAC7B,kBAAkB,CAAC,CAAC;EACpB,IAAI;GAOF,OAAO,cAAa,MANC,eACnB,iBAAiB;IACf,WAAW,EAAE,OAAO;KAAE,WAAW,MAAM,KAAK,QAAQ;KAAW,kBAAkB;IAAG,EAAE;IACtF,oBAAoB,wBAAwB,MAAM,MAAM,MAAM,MAAM,EAAE;GACxE,CAAC,CACH,EAAA,CAC2B,MAAM,0BAA0B,cAAc,CAAC,CAAC;EAC7E,SAAS,OAAO;GACd,QAAQ,MAAM,2DAA2D,KAAK;GAC9E,OAAO,QAAQ,CAAC,kBAAkB,CAAC;EACrC;CACF,GACA,CAAC,aAAa,gBAAgB,CAChC;CAMA,MAAM,cAAc,YAClB,OAAO,YAAY;EACjB,oBAAoB,CAAC,CAAC;EACtB,IAAI;GACF,MAAM,SAAS,MAAM,eAAe,oBAAoB,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,QAAQ,KAAK,EAAE,EAAE,CAAC,CAAC;GACzG,cAAc,OAAO,MAAM,iBAAiB,MAAM,MAAM,IAAI;GAC5D,OAAO,mBAAmB,OAAO,MAAM,iBAAiB,cAAc,CAAC,CAAC;EAC1E,SAAS,OAAO;GACd,QAAQ,MAAM,6CAA6C,KAAK;GAChE,OAAO,cAAc,CAAC,kBAAkB,CAAC;EAC3C;CACF,GACA,CAAC,mBAAmB,CACtB;CAEA,MAAM,eAAe,YACnB,OAAO,YAAY;EACjB,oBAAoB,CAAC,CAAC;EACtB,IAAI;GACF,MAAM,SAAS,MAAM,eAAe,qBAAqB,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,QAAQ,KAAK,EAAE,EAAE,CAAC,CAAC;GAC1G,cAAc,OAAO,MAAM,kBAAkB,MAAM,MAAM,IAAI;GAC7D,OAAO,mBAAmB,OAAO,MAAM,kBAAkB,cAAc,CAAC,CAAC;EAC3E,SAAS,OAAO;GACd,QAAQ,MAAM,+CAA+C,KAAK;GAClE,OAAO,cAAc,CAAC,kBAAkB,CAAC;EAC3C;CACF,GACA,CAAC,oBAAoB,CACvB;CAQA,MAAM,WAAW,YAAqC,YAAY;EAChE,IAAI;GACF,MAAM,SAAS,MAAM,iBAAiB;GAItC,cAAc,OAAO,MAAM,cAAc,MAAM,MAAM,IAAI;GACzD,MAAM,SAAS,OAAO,MAAM,cAAc,cAAc,CAAC;GACzD,IAAI,OAAO,WAAW,GAAG,sBAAsB;GAC/C,OAAO;EACT,SAAS,OAAO;GACd,QAAQ,MAAM,uCAAuC,KAAK;GAC1D,OAAO,CAAC,kBAAkB;EAC5B;CACF,GAAG,CAAC,gBAAgB,CAAC;CAErB,OAAO,eACE;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,IACA;EAAC;EAAS;EAAY;EAAa;EAAkB;EAAkB;EAAa;EAAc;CAAQ,CAC5G;AACF;AAQ2B,cAAc;CACvC,WAAW;CACX,aAAa;CACb,UAAU;EAAE,MAAM;EAAsB,YAAY;CAAkC;CACtF,SAAS;EACP,SAAS;GACP,aAAa;GACb,SAAS;IACP;KAAE,MAAM;KAAa,MAAM;IAAS;IACpC;KAAE,MAAM;KAAU,MAAM;IAAS;IACjC;KAAE,MAAM;KAAY,MAAM;KAAU,UAAU;IAAK;GACrD;EACF;EACA,YAAY;GACV,aAAa;GACb,SAAS,CAAC;IAAE,MAAM;IAAM,MAAM;GAAS,CAAC;EAC1C;EACA,aAAa;GACX,aAAa;GACb,SAAS,CAAC;IAAE,MAAM;IAAO,MAAM;GAAW,CAAC;EAC7C;EACA,kBAAkB;GAChB,aAAa;GACb,SAAS,CACP;IAAE,MAAM;IAAM,MAAM;GAAS,GAC7B;IAAE,MAAM;IAAM,MAAM;IAAU,UAAU;GAAK,CAC/C;EACF;EACA,kBAAkB;GAChB,aAAa;GACb,SAAS,CACP;IAAE,MAAM;IAAM,MAAM;GAAS,GAC7B;IAAE,MAAM;IAAM,MAAM;IAAU,UAAU;GAAK,CAC/C;EACF;EACA,aAAa;GACX,aAAa;GACb,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;GAAS,CAAC;EAC5C;EACA,cAAc;GACZ,aAAa;GACb,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;GAAS,CAAC;EAC5C;EACA,UAAU;GACR,aACE;GACF,SAAS,CAAC;EACZ;CACF;CACA,KAAK;AACP,CAAC"}
|
|
@@ -5,7 +5,7 @@ import { useApolloClient, useReactiveVar } from "@apollo/client/react";
|
|
|
5
5
|
import { useEffect, useRef } from "react";
|
|
6
6
|
//#region src/sdk/cart/use-cart-validate-on-open.ts
|
|
7
7
|
/**
|
|
8
|
-
* Runs stock validation when the mini-cart overlay opens (
|
|
8
|
+
* Runs stock validation when the mini-cart overlay opens (docs/storefront-core/reference-cart.md § Validation).
|
|
9
9
|
*
|
|
10
10
|
* `miniCartOpenVar` is the overlay's single source of truth — the add-pulse open, the header trigger,
|
|
11
11
|
* and manual close all route through it — so watching its closed→open edge covers every way the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-cart-validate-on-open.js","names":[],"sources":["../../../src/sdk/cart/use-cart-validate-on-open.ts"],"sourcesContent":["'use client';\n\nimport { useApolloClient, useReactiveVar } from '@apollo/client/react';\nimport { useEffect, useRef } from 'react';\nimport {\n CART_VALIDATE_STALENESS_MS,\n cartAvailabilityFreshAtVar,\n cartWritesInFlightVar,\n miniCartOpenVar,\n} from '../state/cart.js';\nimport { readCartFromCache } from './use-cart-actions.js';\n\n/**\n * Runs stock validation when the mini-cart overlay opens (
|
|
1
|
+
{"version":3,"file":"use-cart-validate-on-open.js","names":[],"sources":["../../../src/sdk/cart/use-cart-validate-on-open.ts"],"sourcesContent":["'use client';\n\nimport { useApolloClient, useReactiveVar } from '@apollo/client/react';\nimport { useEffect, useRef } from 'react';\nimport {\n CART_VALIDATE_STALENESS_MS,\n cartAvailabilityFreshAtVar,\n cartWritesInFlightVar,\n miniCartOpenVar,\n} from '../state/cart.js';\nimport { readCartFromCache } from './use-cart-actions.js';\n\n/**\n * Runs stock validation when the mini-cart overlay opens (docs/storefront-core/reference-cart.md § Validation).\n *\n * `miniCartOpenVar` is the overlay's single source of truth — the add-pulse open, the header trigger,\n * and manual close all route through it — so watching its closed→open edge covers every way the\n * drawer opens. The call is bounded and cooperative: it fires at most once per transition, and skips\n * when there is nothing to gain — no validate action wired, a cart write in flight, availability\n * refreshed within the staleness window (which is why open-on-add does not re-validate), or an empty\n * cart. `validate` is the compiler-injected `$actions.cart.validate`; when it is absent the hook is a\n * no-op, so a store without the action wired pays nothing.\n *\n * A dispatch is reserved synchronously with a ref latch, cleared when the call settles. Freshness is\n * only stamped once `validate` resolves, so a close-and-reopen during that window would otherwise slip\n * past the staleness guard and fire a second call; the latch closes that gap.\n */\nexport function useCartValidateOnOpen(validate: (() => Promise<unknown>) | undefined): void {\n const client = useApolloClient();\n const open = useReactiveVar(miniCartOpenVar);\n const wasOpen = useRef(open);\n const pending = useRef(false);\n\n useEffect(() => {\n const opened = open && !wasOpen.current;\n wasOpen.current = open;\n if (!opened || !validate || pending.current) return;\n // A write in flight (or one that just settled, inside the window) already refreshed availability.\n if (cartWritesInFlightVar() > 0) return;\n if (Date.now() - cartAvailabilityFreshAtVar() < CART_VALIDATE_STALENESS_MS) return;\n // Nothing to validate on an empty cart, and T1 would only round-trip a single read for it.\n const cart = readCartFromCache(client);\n if (!cart || cart.items.length === 0) return;\n pending.current = true;\n // The current wiring never rejects (useCartActions().validate catches and resolves with\n // [NETWORK_USER_ERROR]); this guards a differently-injected validate so a rejection cannot\n // escape as an unhandled promise rejection.\n void Promise.resolve(validate())\n .catch((error) => {\n console.error('[cart.validateOnOpen] validate rejected', error);\n })\n .finally(() => {\n pending.current = false;\n });\n }, [open, validate, client]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,sBAAsB,UAAsD;CAC1F,MAAM,SAAS,gBAAgB;CAC/B,MAAM,OAAO,eAAe,eAAe;CAC3C,MAAM,UAAU,OAAO,IAAI;CAC3B,MAAM,UAAU,OAAO,KAAK;CAE5B,gBAAgB;EACd,MAAM,SAAS,QAAQ,CAAC,QAAQ;EAChC,QAAQ,UAAU;EAClB,IAAI,CAAC,UAAU,CAAC,YAAY,QAAQ,SAAS;EAE7C,IAAI,sBAAsB,IAAI,GAAG;EACjC,IAAI,KAAK,IAAI,IAAI,2BAA2B,IAAA,KAAgC;EAE5E,MAAM,OAAO,kBAAkB,MAAM;EACrC,IAAI,CAAC,QAAQ,KAAK,MAAM,WAAW,GAAG;EACtC,QAAQ,UAAU;EAIlB,QAAa,QAAQ,SAAS,CAAC,CAAC,CAC7B,OAAO,UAAU;GAChB,QAAQ,MAAM,2CAA2C,KAAK;EAChE,CAAC,CAAC,CACD,cAAc;GACb,QAAQ,UAAU;EACpB,CAAC;CACL,GAAG;EAAC;EAAM;EAAU;CAAM,CAAC;AAC7B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cartAvailabilityFreshAtVar, cartWritesInFlightVar } from "../state/cart.js";
|
|
2
2
|
//#region src/sdk/cart/write-gate.ts
|
|
3
3
|
/**
|
|
4
|
-
* In-flight cart-write registry (
|
|
4
|
+
* In-flight cart-write registry (docs/storefront-core/reference-free-particle-model.md § The cart write contract).
|
|
5
5
|
*
|
|
6
6
|
* The stock-validation wiring needs two things the mutation contract never had to expose, because
|
|
7
7
|
* the open-time validate is the first cart call that is not user-initiated:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write-gate.js","names":[],"sources":["../../../src/sdk/cart/write-gate.ts"],"sourcesContent":["import { cartAvailabilityFreshAtVar, cartWritesInFlightVar } from '../state/cart.js';\n\n/**\n * In-flight cart-write registry (
|
|
1
|
+
{"version":3,"file":"write-gate.js","names":[],"sources":["../../../src/sdk/cart/write-gate.ts"],"sourcesContent":["import { cartAvailabilityFreshAtVar, cartWritesInFlightVar } from '../state/cart.js';\n\n/**\n * In-flight cart-write registry (docs/storefront-core/reference-free-particle-model.md § The cart write contract).\n *\n * The stock-validation wiring needs two things the mutation contract never had to expose, because\n * the open-time validate is the first cart call that is not user-initiated:\n *\n * - an observable \"is a write running\" signal, so the open call can *skip* while one is (that\n * write's response already carries current availability); and\n * - an `await`-settlement primitive, so the pre-checkout call can *wait* for any write before it\n * re-checks the cart it is about to send to checkout.\n *\n * Both derive from one module-scoped set of pending write promises. `validate` is read-only upstream\n * and must never register here — it would gate itself.\n */\n\nconst inFlight = new Set<Promise<unknown>>();\n\n/**\n * Stamp \"availability is fresh as of now\". Called only where the client actually received current\n * availability: a cart write that came back clean, or a successful `validate`. A write answered with\n * `userErrors` carries `cart: null` (mutation contract), so it learned nothing and must not stamp —\n * otherwise the next overlay open would skip validation on the strength of a rejected write.\n */\nexport function markAvailabilityFresh(): void {\n cartAvailabilityFreshAtVar(Date.now());\n}\n\n/**\n * Register a cart write's promise for the lifetime of the call, publishing the reactive in-flight\n * count so the validate-on-open call can skip while a write is outstanding. Tracking only — the\n * freshness stamp belongs to the caller, which is the only place that can see whether the payload\n * came back clean. Returns the same promise so callers can `await trackCartWrite(mutation(...))`.\n */\nexport function trackCartWrite<T>(promise: Promise<T>): Promise<T> {\n inFlight.add(promise);\n cartWritesInFlightVar(inFlight.size);\n const clear = () => {\n inFlight.delete(promise);\n cartWritesInFlightVar(inFlight.size);\n };\n promise.then(clear, clear);\n return promise;\n}\n\n/**\n * Resolve once every write registered at call time has settled (fulfilled or rejected). Writes\n * started afterwards are not awaited — this captures \"any in-flight write\" at the moment the\n * pre-checkout call fires, which is exactly the ordering guarantee the checkout gate needs.\n */\nexport function awaitCartWrites(): Promise<void> {\n return Promise.allSettled(inFlight).then(() => undefined);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAM,2BAAW,IAAI,IAAsB;;;;;;;AAQ3C,SAAgB,wBAA8B;CAC5C,2BAA2B,KAAK,IAAI,CAAC;AACvC;;;;;;;AAQA,SAAgB,eAAkB,SAAiC;CACjE,SAAS,IAAI,OAAO;CACpB,sBAAsB,SAAS,IAAI;CACnC,MAAM,cAAc;EAClB,SAAS,OAAO,OAAO;EACvB,sBAAsB,SAAS,IAAI;CACrC;CACA,QAAQ,KAAK,OAAO,KAAK;CACzB,OAAO;AACT;;;;;;AAOA,SAAgB,kBAAiC;CAC/C,OAAO,QAAQ,WAAW,QAAQ,CAAC,CAAC,WAAW,KAAA,CAAS;AAC1D"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
//#region src/sdk/data/entity-resolution-context.ts
|
|
4
|
+
/**
|
|
5
|
+
* Is the `$data` reaching this subtree the cache's own answer for the entity the page query
|
|
6
|
+
* currently points at, or the server-rendered `fallbackData` standing in for one?
|
|
7
|
+
*
|
|
8
|
+
* `DataOverrideContext` carries the slice and cannot carry this: a fallback slice and a resolved
|
|
9
|
+
* one are the same shape, and an entity whose saved values are genuinely absent looks exactly like
|
|
10
|
+
* an entity whose cache read regressed. Only `useReactiveEntity` knows which of the two it just
|
|
11
|
+
* handed down, so it is the one that publishes here — see its `resolved` return.
|
|
12
|
+
*
|
|
13
|
+
* Default `true`: with no reactive provider above, nothing is outstanding. Storybook, unit tests and
|
|
14
|
+
* any tree handing `$data` over as a plain prop read the default and gate nothing, exactly as they
|
|
15
|
+
* do with `useReactiveDataSettled`.
|
|
16
|
+
*/
|
|
17
|
+
const EntityResolutionContext = createContext(true);
|
|
18
|
+
/**
|
|
19
|
+
* Every entity slice composed into this subtree's `$data` is the cache's own answer.
|
|
20
|
+
*
|
|
21
|
+
* Unlike `useReactiveDataSettled`, this is not latched: it re-arms on its own whenever the entity
|
|
22
|
+
* ref moves or its cache read stops being complete, which is the whole point — a latched page-level
|
|
23
|
+
* signal cannot tell a form that the data under it has gone back to being a placeholder.
|
|
24
|
+
*/
|
|
25
|
+
function useEntityDataResolved() {
|
|
26
|
+
return useContext(EntityResolutionContext);
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { EntityResolutionContext, useEntityDataResolved };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=entity-resolution-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-resolution-context.js","names":[],"sources":["../../../src/sdk/data/entity-resolution-context.ts"],"sourcesContent":["'use client';\n\nimport { createContext, useContext } from 'react';\n\n/**\n * Is the `$data` reaching this subtree the cache's own answer for the entity the page query\n * currently points at, or the server-rendered `fallbackData` standing in for one?\n *\n * `DataOverrideContext` carries the slice and cannot carry this: a fallback slice and a resolved\n * one are the same shape, and an entity whose saved values are genuinely absent looks exactly like\n * an entity whose cache read regressed. Only `useReactiveEntity` knows which of the two it just\n * handed down, so it is the one that publishes here — see its `resolved` return.\n *\n * Default `true`: with no reactive provider above, nothing is outstanding. Storybook, unit tests and\n * any tree handing `$data` over as a plain prop read the default and gate nothing, exactly as they\n * do with `useReactiveDataSettled`.\n */\nexport const EntityResolutionContext = createContext<boolean>(true);\n\n/**\n * Every entity slice composed into this subtree's `$data` is the cache's own answer.\n *\n * Unlike `useReactiveDataSettled`, this is not latched: it re-arms on its own whenever the entity\n * ref moves or its cache read stops being complete, which is the whole point — a latched page-level\n * signal cannot tell a form that the data under it has gone back to being a placeholder.\n */\nexport function useEntityDataResolved(): boolean {\n return useContext(EntityResolutionContext);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAa,0BAA0B,cAAuB,IAAI;;;;;;;;AASlE,SAAgB,wBAAiC;CAC/C,OAAO,WAAW,uBAAuB;AAC3C"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { DataOverrideContext } from "./data-override-context.js";
|
|
3
|
+
import { EntityResolutionContext } from "./entity-resolution-context.js";
|
|
3
4
|
import { useReactiveEntity } from "./use-reactive-entity.js";
|
|
4
|
-
import { useContext } from "react";
|
|
5
|
+
import { useContext, useMemo } from "react";
|
|
5
6
|
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
//#region src/sdk/data/entity-subscriber.tsx
|
|
7
8
|
/**
|
|
@@ -12,14 +13,23 @@ import { jsx } from "react/jsx-runtime";
|
|
|
12
13
|
*/
|
|
13
14
|
function EntitySubscriber({ entityFragment, fragmentName, rootField, fallbackData, reset, children }) {
|
|
14
15
|
const outer = useContext(DataOverrideContext);
|
|
15
|
-
const
|
|
16
|
-
const
|
|
16
|
+
const outerResolved = useContext(EntityResolutionContext);
|
|
17
|
+
const { data, resolved } = useReactiveEntity(entityFragment, fragmentName, rootField, fallbackData);
|
|
18
|
+
const value = useMemo(() => reset ? { [rootField]: data } : {
|
|
17
19
|
...outer && typeof outer === "object" ? outer : {},
|
|
18
|
-
[rootField]:
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
[rootField]: data
|
|
21
|
+
}, [
|
|
22
|
+
reset,
|
|
23
|
+
rootField,
|
|
24
|
+
data,
|
|
25
|
+
outer
|
|
26
|
+
]);
|
|
27
|
+
return /* @__PURE__ */ jsx(EntityResolutionContext, {
|
|
28
|
+
value: reset ? resolved : outerResolved && resolved,
|
|
29
|
+
children: /* @__PURE__ */ jsx(DataOverrideContext, {
|
|
30
|
+
value,
|
|
31
|
+
children
|
|
32
|
+
})
|
|
23
33
|
});
|
|
24
34
|
}
|
|
25
35
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-subscriber.js","names":[],"sources":["../../../src/sdk/data/entity-subscriber.tsx"],"sourcesContent":["'use client';\n\nimport type { DocumentNode } from '@apollo/client';\nimport type { ReactNode } from 'react';\nimport { useContext } from 'react';\nimport { DataOverrideContext } from './data-override-context.js';\nimport { useReactiveEntity } from './use-reactive-entity.js';\n\nexport type EntitySubscriberProps = Readonly<{\n /** Derived `fragment <X>_<EntityType> on <EntityType>` document emitted by the compiler. */\n entityFragment: DocumentNode;\n /** Name of the derived fragment, passed to `useFragment`. */\n fragmentName: string;\n /** Top-level field of the page query (e.g. 'cart') used to extract the entity ref, and the key\n * under which the resolved slice is merged into the composed `$data` object. */\n rootField: string;\n /** Server-rendered fragment payload; used until the cache resolves a complete entity slice. */\n fallbackData: unknown;\n /**\n * When `true`, RESETS the context to `{ [rootField]: resolved }`, discarding any outer\n * `DataOverrideContext` value — this is the outermost subscriber of a component's nested stack,\n * and prevents a parent component's `$data` from leaking into this component's descendants. When\n * `false`, merges this subscriber's slice into the SAME-stack outer value instead.\n */\n reset: boolean;\n children: ReactNode;\n}>;\n\n/**\n * Per-entity subscriber used internally by `MultipleReactiveDataProvider` to compose several\n * entities' cache slices into one keyed `$data` object for a single component. Resolves its own\n * slice exactly like `ReactiveDataProvider` (see `useReactiveEntity`), then merges it under\n * `rootField` into the outer `DataOverrideContext` value.\n */\nexport function EntitySubscriber({\n entityFragment,\n fragmentName,\n rootField,\n fallbackData,\n reset,\n children,\n}: EntitySubscriberProps): ReactNode {\n const outer = useContext(DataOverrideContext);\n const resolved = useReactiveEntity(entityFragment, fragmentName, rootField, fallbackData);\n\n const value =
|
|
1
|
+
{"version":3,"file":"entity-subscriber.js","names":[],"sources":["../../../src/sdk/data/entity-subscriber.tsx"],"sourcesContent":["'use client';\n\nimport type { DocumentNode } from '@apollo/client';\nimport type { ReactNode } from 'react';\nimport { useContext, useMemo } from 'react';\nimport { DataOverrideContext } from './data-override-context.js';\nimport { EntityResolutionContext } from './entity-resolution-context.js';\nimport { useReactiveEntity } from './use-reactive-entity.js';\n\nexport type EntitySubscriberProps = Readonly<{\n /** Derived `fragment <X>_<EntityType> on <EntityType>` document emitted by the compiler. */\n entityFragment: DocumentNode;\n /** Name of the derived fragment, passed to `useFragment`. */\n fragmentName: string;\n /** Top-level field of the page query (e.g. 'cart') used to extract the entity ref, and the key\n * under which the resolved slice is merged into the composed `$data` object. */\n rootField: string;\n /** Server-rendered fragment payload; used until the cache resolves a complete entity slice. */\n fallbackData: unknown;\n /**\n * When `true`, RESETS the context to `{ [rootField]: resolved }`, discarding any outer\n * `DataOverrideContext` value — this is the outermost subscriber of a component's nested stack,\n * and prevents a parent component's `$data` from leaking into this component's descendants. When\n * `false`, merges this subscriber's slice into the SAME-stack outer value instead.\n */\n reset: boolean;\n children: ReactNode;\n}>;\n\n/**\n * Per-entity subscriber used internally by `MultipleReactiveDataProvider` to compose several\n * entities' cache slices into one keyed `$data` object for a single component. Resolves its own\n * slice exactly like `ReactiveDataProvider` (see `useReactiveEntity`), then merges it under\n * `rootField` into the outer `DataOverrideContext` value.\n */\nexport function EntitySubscriber({\n entityFragment,\n fragmentName,\n rootField,\n fallbackData,\n reset,\n children,\n}: EntitySubscriberProps): ReactNode {\n const outer = useContext(DataOverrideContext);\n const outerResolved = useContext(EntityResolutionContext);\n const { data, resolved } = useReactiveEntity(entityFragment, fragmentName, rootField, fallbackData);\n\n // Memoised because this object is the context value: a fresh identity on every render makes every\n // consumer below re-render, and in a form that is every keystroke. Its two sibling providers pass a\n // stable reference already.\n const value = useMemo(\n () => (reset ? { [rootField]: data } : { ...(outer && typeof outer === 'object' ? outer : {}), [rootField]: data }),\n [reset, rootField, data, outer],\n );\n\n // Mirrors the data merge exactly: the composed `$data` is resolved only when every slice merged\n // into it is. `reset` discards the outer verdict for the same reason it discards the outer value —\n // that verdict belongs to an ancestor component's own entities, not to this component's.\n const resolution = reset ? resolved : outerResolved && resolved;\n\n return (\n <EntityResolutionContext value={resolution}>\n <DataOverrideContext value={value}>{children}</DataOverrideContext>\n </EntityResolutionContext>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAmCA,SAAgB,iBAAiB,EAC/B,gBACA,cACA,WACA,cACA,OACA,YACmC;CACnC,MAAM,QAAQ,WAAW,mBAAmB;CAC5C,MAAM,gBAAgB,WAAW,uBAAuB;CACxD,MAAM,EAAE,MAAM,aAAa,kBAAkB,gBAAgB,cAAc,WAAW,YAAY;CAKlG,MAAM,QAAQ,cACL,QAAQ,GAAG,YAAY,KAAK,IAAI;EAAE,GAAI,SAAS,OAAO,UAAU,WAAW,QAAQ,CAAC;GAAK,YAAY;CAAK,GACjH;EAAC;EAAO;EAAW;EAAM;CAAK,CAChC;CAOA,OACE,oBAAC,yBAAD;EAAyB,OAHR,QAAQ,WAAW,iBAAiB;EAInD,UAAA,oBAAC,qBAAD;GAA4B;GAAQ;EAA8B,CAAA;CAC3C,CAAA;AAE7B"}
|
package/dist/sdk/data/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { DataOverrideContext, useDataOverride } from "./data-override-context.js
|
|
|
2
2
|
import { HotDataProvider } from "./hot-data-provider.js";
|
|
3
3
|
import { ListItemRefContext, useListItemRef } from "./list-item-ref-context.js";
|
|
4
4
|
import { PageReactiveDataRoot, ReactiveRootContext } from "./page-reactive-data-root.js";
|
|
5
|
+
import "./entity-resolution-context.js";
|
|
5
6
|
import { MultipleReactiveDataProvider } from "./multiple-reactive-data-provider.js";
|
|
6
7
|
import { ReactiveDataProvider } from "./reactive-data-provider.js";
|
|
7
8
|
import { ReactiveListItemDataProvider } from "./reactive-list-item-data-provider.js";
|
|
@@ -30,9 +30,27 @@ export type ReactiveRootContextValue<TData = unknown> = Readonly<{
|
|
|
30
30
|
/**
|
|
31
31
|
* Error from the underlying useQuery. With non-null root fields (e.g. `cart: Cart!`), a failed
|
|
32
32
|
* fetch nulls the whole operation's `data` — descendants need this to tell "fetch failed" apart
|
|
33
|
-
* from "entity is legitimately absent" (
|
|
33
|
+
* from "entity is legitimately absent" (docs/storefront-core/reference-free-particle-model.md § Read side — data).
|
|
34
34
|
*/
|
|
35
35
|
error: ErrorLike | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* True only under a real `PageReactiveDataRoot`. Every other field above is ambiguous between
|
|
38
|
+
* "no root at all" and "a root with nothing to give right now" — `rootQueryData: undefined` and
|
|
39
|
+
* `loading: false` describe both the context default and a root whose query is skipped for want
|
|
40
|
+
* of a reactive variable. `useReactiveEntity` needs that distinction to tell a placeholder slice
|
|
41
|
+
* apart from the only slice there is.
|
|
42
|
+
*
|
|
43
|
+
* Required, not optional, and deliberately so. This flag is the whole of what arms the gate in
|
|
44
|
+
* `useReactiveEntity`, so a provider that omits it disarms the gate silently — measured: a value
|
|
45
|
+
* that `satisfies ReactiveRootContextValue` without it reproduces the corrupting payload byte for
|
|
46
|
+
* byte on an otherwise-fixed tree, and no test fails. `ReactiveRootContext` is public surface via
|
|
47
|
+
* `sdk/data/index.ts`, so that provider need not live in this repo. Making it required moves that
|
|
48
|
+
* failure to compile time.
|
|
49
|
+
*
|
|
50
|
+
* A hand-built context value stands in for a root's *output*, not for the reactive runtime, so it
|
|
51
|
+
* declares `false` and its subtree keeps gating on nothing.
|
|
52
|
+
*/
|
|
53
|
+
hasRoot: boolean;
|
|
36
54
|
}>;
|
|
37
55
|
export declare const ReactiveRootContext: import("react").Context<Readonly<{
|
|
38
56
|
/** Current root query data exposed to descendant `ReactiveDataProvider`s. */
|
|
@@ -44,9 +62,27 @@ export declare const ReactiveRootContext: import("react").Context<Readonly<{
|
|
|
44
62
|
/**
|
|
45
63
|
* Error from the underlying useQuery. With non-null root fields (e.g. `cart: Cart!`), a failed
|
|
46
64
|
* fetch nulls the whole operation's `data` — descendants need this to tell "fetch failed" apart
|
|
47
|
-
* from "entity is legitimately absent" (
|
|
65
|
+
* from "entity is legitimately absent" (docs/storefront-core/reference-free-particle-model.md § Read side — data).
|
|
48
66
|
*/
|
|
49
67
|
error: ErrorLike | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* True only under a real `PageReactiveDataRoot`. Every other field above is ambiguous between
|
|
70
|
+
* "no root at all" and "a root with nothing to give right now" — `rootQueryData: undefined` and
|
|
71
|
+
* `loading: false` describe both the context default and a root whose query is skipped for want
|
|
72
|
+
* of a reactive variable. `useReactiveEntity` needs that distinction to tell a placeholder slice
|
|
73
|
+
* apart from the only slice there is.
|
|
74
|
+
*
|
|
75
|
+
* Required, not optional, and deliberately so. This flag is the whole of what arms the gate in
|
|
76
|
+
* `useReactiveEntity`, so a provider that omits it disarms the gate silently — measured: a value
|
|
77
|
+
* that `satisfies ReactiveRootContextValue` without it reproduces the corrupting payload byte for
|
|
78
|
+
* byte on an otherwise-fixed tree, and no test fails. `ReactiveRootContext` is public surface via
|
|
79
|
+
* `sdk/data/index.ts`, so that provider need not live in this repo. Making it required moves that
|
|
80
|
+
* failure to compile time.
|
|
81
|
+
*
|
|
82
|
+
* A hand-built context value stands in for a root's *output*, not for the reactive runtime, so it
|
|
83
|
+
* declares `false` and its subtree keeps gating on nothing.
|
|
84
|
+
*/
|
|
85
|
+
hasRoot: boolean;
|
|
50
86
|
}>>;
|
|
51
87
|
export declare function PageReactiveDataRoot<TData = unknown, TVars extends OperationVariables = OperationVariables>(props: PageReactiveDataRootProps<TData, TVars>): ReactNode;
|
|
52
88
|
//#endregion
|
|
@@ -15,7 +15,8 @@ const ReactiveRootContext = createContext({
|
|
|
15
15
|
rootQueryData: void 0,
|
|
16
16
|
loading: false,
|
|
17
17
|
hasAllRequiredVariables: false,
|
|
18
|
-
error: void 0
|
|
18
|
+
error: void 0,
|
|
19
|
+
hasRoot: false
|
|
19
20
|
});
|
|
20
21
|
function getOperationDefinition(query) {
|
|
21
22
|
for (const definition of query.definitions) if (definition.kind === Kind.OPERATION_DEFINITION) return definition;
|
|
@@ -135,7 +136,8 @@ function PageReactiveDataRoot(props) {
|
|
|
135
136
|
rootQueryData: data ?? void 0,
|
|
136
137
|
loading,
|
|
137
138
|
hasAllRequiredVariables,
|
|
138
|
-
error
|
|
139
|
+
error,
|
|
140
|
+
hasRoot: true
|
|
139
141
|
}), [
|
|
140
142
|
data,
|
|
141
143
|
loading,
|