@vtex/faststore 0.0.7 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/dist/actions.json +194 -1
- package/dist/catalog.json +3449 -1612
- package/dist/cli/index.d.ts +512 -63
- package/dist/cli/index.js +2098 -686
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/server-runtime.js +44 -24
- package/dist/cli/server-runtime.js.map +1 -1
- package/dist/client.d.ts +5 -1
- package/dist/client.js +5 -1
- package/dist/components.d.ts +42 -10
- package/dist/components.js +47 -15
- package/dist/content-types.json +823 -0
- package/dist/core/dist/define-component.d.ts +12 -2
- package/dist/docs/account-access-gate.md +4 -4
- package/dist/docs/account-address-book.md +519 -0
- package/dist/docs/account-entry-point.md +5 -5
- package/dist/docs/account-menu.md +7 -7
- package/dist/docs/account-saved-cards.md +301 -0
- package/dist/docs/alert-dialog.md +2 -2
- package/dist/docs/billing-address.md +17 -9
- package/dist/docs/binding-switcher.md +154 -0
- package/dist/docs/card-entry.md +38 -7
- package/dist/docs/card-summary.md +175 -0
- package/dist/docs/cardholder-document.md +16 -14
- package/dist/docs/cart-destination.md +238 -0
- package/dist/docs/cart-item.md +22 -4
- package/dist/docs/cart-totalizer.md +1 -1
- package/dist/docs/cart.md +126 -19
- package/dist/docs/client-profile-fieldset.md +7 -5
- package/dist/docs/copy-text.md +84 -0
- package/dist/docs/document-field.md +8 -8
- package/dist/docs/footer-link-group.md +2 -2
- package/dist/docs/footer-social.md +1 -1
- package/dist/docs/free-shipping-progress.md +13 -9
- package/dist/docs/header-root.md +1 -1
- package/dist/docs/incentives.md +4 -4
- package/dist/docs/logo.md +6 -6
- package/dist/docs/my-account-profile.md +4 -4
- package/dist/docs/navigation-link-list.md +2 -2
- package/dist/docs/navigation-link.md +3 -3
- package/dist/docs/order-cancel.md +52 -0
- package/dist/docs/order-delivery-address.md +63 -0
- package/dist/docs/order-delivery-group.md +83 -0
- package/dist/docs/order-details-back.md +41 -0
- package/dist/docs/order-details-header.md +48 -0
- package/dist/docs/order-list-row.md +46 -0
- package/dist/docs/order-list-search.md +101 -0
- package/dist/docs/order-list-views.md +97 -0
- package/dist/docs/order-list.md +94 -0
- package/dist/docs/order-payment.md +94 -0
- package/dist/docs/order-placed-date.md +49 -0
- package/dist/docs/order-placed-payment-summary.md +13 -15
- package/dist/docs/order-placed.md +225 -0
- package/dist/docs/order-progress.md +88 -0
- package/dist/docs/order-status-badge.md +91 -0
- package/dist/docs/order-summary.md +53 -0
- package/dist/docs/payment-method-badges.md +16 -5
- package/dist/docs/payment-methods.md +1 -1
- package/dist/docs/place-order.md +13 -9
- package/dist/docs/product-information.md +2 -2
- package/dist/docs/product-shelf.md +7 -0
- package/dist/docs/saved-cards.md +2 -2
- package/dist/docs/select.md +22 -10
- package/dist/docs/session.md +1 -1
- package/dist/docs/shipping-address.md +1 -1
- package/dist/docs/sign-in-redirect.md +7 -7
- package/dist/docs/toast.md +35 -13
- package/dist/index.d.ts +7 -2
- package/dist/index.js +2 -1
- package/dist/internal/client.d.ts +9 -1
- package/dist/internal/client.js +10 -2
- package/dist/internal/server.d.ts +8 -2
- package/dist/internal/server.js +8 -2
- package/dist/matter/account-access-gate/account-access-gate.d.ts +2 -1
- package/dist/matter/account-access-gate/account-access-gate.js +19 -16
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -1
- package/dist/matter/account-address-book/account-address-book-delete.d.ts +60 -0
- package/dist/matter/account-address-book/account-address-book-delete.js +184 -0
- package/dist/matter/account-address-book/account-address-book-delete.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.d.ts +19 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.js +50 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.d.ts +42 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.js +146 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.d.ts +25 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.js +72 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book.d.ts +120 -0
- package/dist/matter/account-address-book/account-address-book.js +469 -0
- package/dist/matter/account-address-book/account-address-book.js.map +1 -0
- package/dist/matter/account-address-book/account-address-country-switch.js +59 -0
- package/dist/matter/account-address-book/account-address-country-switch.js.map +1 -0
- package/dist/matter/account-address-book/account-address-country.d.ts +36 -0
- package/dist/matter/account-address-book/account-address-country.js +151 -0
- package/dist/matter/account-address-book/account-address-country.js.map +1 -0
- package/dist/matter/account-address-book/account-address-first-line.js +11 -0
- package/dist/matter/account-address-book/account-address-first-line.js.map +1 -0
- package/dist/matter/account-address-book/account-address-form-context.d.ts +55 -0
- package/dist/matter/account-address-book/account-address-form-context.js +14 -0
- package/dist/matter/account-address-book/account-address-form-context.js.map +1 -0
- package/dist/matter/account-address-book/account-address-form.d.ts +85 -0
- package/dist/matter/account-address-book/account-address-form.js +946 -0
- package/dist/matter/account-address-book/account-address-form.js.map +1 -0
- package/dist/matter/account-address-book/account-address-postal-autofill.js +189 -0
- package/dist/matter/account-address-book/account-address-postal-autofill.js.map +1 -0
- package/dist/matter/account-address-book/account-address-record.d.ts +26 -0
- package/dist/matter/account-address-book/account-address-record.js +138 -0
- package/dist/matter/account-address-book/account-address-record.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.js +11 -11
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +7 -3
- package/dist/matter/account-menu/account-menu.js +69 -28
- package/dist/matter/account-menu/account-menu.js.map +1 -1
- package/dist/matter/account-saved-cards/account-saved-cards.d.ts +383 -0
- package/dist/matter/account-saved-cards/account-saved-cards.js +956 -0
- package/dist/matter/account-saved-cards/account-saved-cards.js.map +1 -0
- package/dist/matter/billing-address/billing-address.d.ts +30 -17
- package/dist/matter/billing-address/billing-address.js +128 -33
- package/dist/matter/billing-address/billing-address.js.map +1 -1
- package/dist/matter/breadcrumb/breadcrumb.js +2 -2
- package/dist/matter/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/matter/card-entry/card-capture.js +4 -3
- package/dist/matter/card-entry/card-capture.js.map +1 -1
- package/dist/matter/card-entry/card-entry.d.ts +5 -3
- package/dist/matter/card-entry/card-entry.js +77 -27
- package/dist/matter/card-entry/card-entry.js.map +1 -1
- package/dist/matter/cardholder-document/cardholder-document.d.ts +10 -9
- package/dist/matter/cardholder-document/cardholder-document.js +36 -16
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -1
- package/dist/matter/cart/cart-namespace.d.ts +5 -1
- package/dist/matter/cart/cart-namespace.js +5 -1
- package/dist/matter/cart/cart-namespace.js.map +1 -1
- package/dist/matter/cart/cart.d.ts +41 -6
- package/dist/matter/cart/cart.js +267 -54
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/cart-destination/cart-destination.d.ts +222 -0
- package/dist/matter/cart-destination/cart-destination.js +613 -0
- package/dist/matter/cart-destination/cart-destination.js.map +1 -0
- package/dist/matter/cart-item/cart-item.d.ts +4 -2
- package/dist/matter/cart-item/cart-item.js +76 -17
- package/dist/matter/cart-item/cart-item.js.map +1 -1
- package/dist/matter/cart-totalizer/cart-totalizer.js +3 -2
- package/dist/matter/cart-totalizer/cart-totalizer.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.js +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.js +8 -5
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -1
- package/dist/matter/free-shipping-progress/free-shipping-progress.d.ts +6 -3
- package/dist/matter/free-shipping-progress/free-shipping-progress.js +76 -33
- package/dist/matter/free-shipping-progress/free-shipping-progress.js.map +1 -1
- package/dist/matter/identity-edit/identity-edit.js +3 -3
- 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/my-account-profile/my-account-profile.d.ts +3 -3
- package/dist/matter/my-account-profile/my-account-profile.js +4 -4
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-cancel/order-cancel.d.ts +110 -0
- package/dist/matter/order-cancel/order-cancel.js +344 -0
- package/dist/matter/order-cancel/order-cancel.js.map +1 -0
- package/dist/matter/order-delivery-address/order-delivery-address.d.ts +73 -0
- package/dist/matter/order-delivery-address/order-delivery-address.js +172 -0
- package/dist/matter/order-delivery-address/order-delivery-address.js.map +1 -0
- package/dist/matter/order-delivery-group/order-delivery-group.d.ts +128 -0
- package/dist/matter/order-delivery-group/order-delivery-group.js +408 -0
- package/dist/matter/order-delivery-group/order-delivery-group.js.map +1 -0
- package/dist/matter/order-details-header/order-details-header.d.ts +104 -0
- package/dist/matter/order-details-header/order-details-header.js +190 -0
- package/dist/matter/order-details-header/order-details-header.js.map +1 -0
- package/dist/matter/order-list/customer-orders-filter.d.ts +12 -0
- package/dist/matter/order-list/customer-orders-filter.js +30 -0
- package/dist/matter/order-list/customer-orders-filter.js.map +1 -0
- package/dist/matter/order-list/order-list.d.ts +121 -0
- package/dist/matter/order-list/order-list.js +395 -0
- package/dist/matter/order-list/order-list.js.map +1 -0
- package/dist/matter/order-list-row/order-list-row.d.ts +44 -0
- package/dist/matter/order-list-row/order-list-row.js +127 -0
- package/dist/matter/order-list-row/order-list-row.js.map +1 -0
- package/dist/matter/order-list-search/order-list-search.d.ts +72 -0
- package/dist/matter/order-list-search/order-list-search.js +296 -0
- package/dist/matter/order-list-search/order-list-search.js.map +1 -0
- package/dist/matter/order-list-views/order-list-views.d.ts +65 -0
- package/dist/matter/order-list-views/order-list-views.js +154 -0
- package/dist/matter/order-list-views/order-list-views.js.map +1 -0
- package/dist/matter/order-payment/order-payment.d.ts +83 -0
- package/dist/matter/order-payment/order-payment.js +258 -0
- package/dist/matter/order-payment/order-payment.js.map +1 -0
- package/dist/matter/order-placed/order-placed-deliveries.d.ts +145 -0
- package/dist/matter/order-placed/order-placed-deliveries.js +380 -0
- package/dist/matter/order-placed/order-placed-deliveries.js.map +1 -0
- package/dist/matter/order-placed/order-placed.d.ts +122 -0
- package/dist/matter/order-placed/order-placed.js +452 -0
- package/dist/matter/order-placed/order-placed.js.map +1 -0
- package/dist/matter/order-placed-date/order-placed-date.d.ts +45 -0
- package/dist/matter/order-placed-date/order-placed-date.js +92 -0
- package/dist/matter/order-placed-date/order-placed-date.js.map +1 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +5 -8
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +29 -81
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -1
- package/dist/matter/order-progress/order-progress.d.ts +85 -0
- package/dist/matter/order-progress/order-progress.js +203 -0
- package/dist/matter/order-progress/order-progress.js.map +1 -0
- package/dist/matter/order-summary/order-summary.d.ts +81 -0
- package/dist/matter/order-summary/order-summary.js +192 -0
- package/dist/matter/order-summary/order-summary.js.map +1 -0
- package/dist/matter/payment/cardholder-document-rule.d.ts +35 -0
- package/dist/matter/payment/cardholder-document-rule.js +35 -0
- package/dist/matter/payment/cardholder-document-rule.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.js +38 -23
- package/dist/matter/payment/payment-card-capture.js.map +1 -1
- package/dist/matter/payment/payment-controller.js +11 -0
- package/dist/matter/payment/payment-controller.js.map +1 -1
- package/dist/matter/payment/payment-method-badges.js +4 -2
- package/dist/matter/payment/payment-method-badges.js.map +1 -1
- package/dist/matter/payment/payment.js +11 -0
- package/dist/matter/payment/payment.js.map +1 -1
- package/dist/matter/payment/use-payment-method-selection.d.ts +39 -0
- package/dist/matter/payment/use-payment-method-selection.js +30 -2
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -1
- package/dist/matter/payment/use-payment-submit-gate.d.ts +16 -2
- package/dist/matter/payment/use-payment-submit-gate.js +42 -14
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -1
- package/dist/matter/place-order/place-order.d.ts +12 -7
- package/dist/matter/place-order/place-order.js +126 -103
- package/dist/matter/place-order/place-order.js.map +1 -1
- package/dist/matter/pricing/pricing.js +5 -5
- package/dist/matter/pricing/pricing.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +7 -0
- package/dist/matter/product-shelf/product-shelf.js +115 -1
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/saved-cards/saved-cards.js +26 -15
- package/dist/matter/saved-cards/saved-cards.js.map +1 -1
- package/dist/matter/session/session.js +2 -2
- package/dist/matter/session/session.js.map +1 -1
- package/dist/matter/shipping-address/country-descriptors.d.ts +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +23 -1
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.js +62 -14
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.d.ts +2 -2
- package/dist/matter/shipping-simulator/shipping-simulator.js +6 -2
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/matter/sign-in-redirect/sign-in-redirect.js +48 -10
- package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +2 -2
- package/dist/matter/variant-selector/variant-selector.js.map +1 -1
- package/dist/particles/address-summary/address-summary.js +2 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +5 -1
- package/dist/particles/alert-dialog/alert-dialog.js +5 -2
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/particles/binding-switcher/binding-switcher.d.ts +38 -0
- package/dist/particles/binding-switcher/binding-switcher.js +183 -0
- package/dist/particles/binding-switcher/binding-switcher.js.map +1 -0
- package/dist/particles/card-summary/card-network.d.ts +17 -0
- package/dist/particles/card-summary/card-network.js +25 -0
- package/dist/particles/card-summary/card-network.js.map +1 -0
- package/dist/particles/card-summary/card-summary.d.ts +83 -0
- package/dist/particles/card-summary/card-summary.js +188 -0
- package/dist/particles/card-summary/card-summary.js.map +1 -0
- package/dist/particles/copy-text/copy-text.d.ts +28 -0
- package/dist/particles/copy-text/copy-text.js +140 -0
- package/dist/particles/copy-text/copy-text.js.map +1 -0
- package/dist/particles/document-field/document-field.d.ts +34 -5
- package/dist/particles/document-field/document-field.js +115 -19
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/field.js +1 -1
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/footer-link-group/footer-link-group.d.ts +6 -2
- package/dist/particles/footer-link-group/footer-link-group.js +18 -7
- package/dist/particles/footer-link-group/footer-link-group.js.map +1 -1
- package/dist/particles/footer-social/footer-social.d.ts +1 -1
- package/dist/particles/footer-social/footer-social.js +1 -1
- package/dist/particles/footer-social/footer-social.js.map +1 -1
- package/dist/particles/incentives/incentives.d.ts +7 -3
- package/dist/particles/incentives/incentives.js +8 -3
- package/dist/particles/incentives/incentives.js.map +1 -1
- package/dist/particles/logo/logo.d.ts +3 -2
- package/dist/particles/logo/logo.js +20 -8
- package/dist/particles/logo/logo.js.map +1 -1
- package/dist/particles/navigation-link/navigation-link.d.ts +2 -3
- package/dist/particles/navigation-link/navigation-link.js +4 -4
- package/dist/particles/navigation-link/navigation-link.js.map +1 -1
- package/dist/particles/navigation-link-list/navigation-link-list.d.ts +6 -2
- package/dist/particles/navigation-link-list/navigation-link-list.js +18 -7
- package/dist/particles/navigation-link-list/navigation-link-list.js.map +1 -1
- package/dist/particles/order-details-back/order-details-back.d.ts +28 -0
- package/dist/particles/order-details-back/order-details-back.js +52 -0
- package/dist/particles/order-details-back/order-details-back.js.map +1 -0
- package/dist/particles/order-status-badge/order-status-badge.d.ts +19 -0
- package/dist/particles/order-status-badge/order-status-badge.js +50 -0
- package/dist/particles/order-status-badge/order-status-badge.js.map +1 -0
- package/dist/particles/payment-methods/payment-methods.d.ts +1 -1
- package/dist/particles/payment-methods/payment-methods.js +1 -1
- package/dist/particles/payment-methods/payment-methods.js.map +1 -1
- package/dist/particles/phone-field/phone-field.js +2 -2
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/select/select.js +6 -6
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/toast/toast.d.ts +21 -7
- package/dist/particles/toast/toast.js +76 -8
- package/dist/particles/toast/toast.js.map +1 -1
- package/dist/schema.graphql +1376 -126
- package/dist/sdk/actions/address-action-provider.d.ts +16 -0
- package/dist/sdk/actions/address-action-provider.js +20 -0
- package/dist/sdk/actions/address-action-provider.js.map +1 -0
- package/dist/sdk/actions/analytics-action-provider.js +1 -1
- package/dist/sdk/actions/index.d.ts +5 -1
- package/dist/sdk/actions/index.js +5 -1
- package/dist/sdk/actions/locale-action-provider.d.ts +16 -0
- package/dist/sdk/actions/locale-action-provider.js +20 -0
- package/dist/sdk/actions/locale-action-provider.js.map +1 -0
- package/dist/sdk/actions/order-action-provider.d.ts +12 -0
- package/dist/sdk/actions/order-action-provider.js +16 -0
- package/dist/sdk/actions/order-action-provider.js.map +1 -0
- package/dist/sdk/actions/saved-card-action-provider.d.ts +12 -0
- package/dist/sdk/actions/saved-card-action-provider.js +16 -0
- package/dist/sdk/actions/saved-card-action-provider.js.map +1 -0
- package/dist/sdk/actions/types.d.ts +10 -2
- package/dist/sdk/address/address-fields.js +38 -0
- package/dist/sdk/address/address-fields.js.map +1 -0
- package/dist/sdk/address/checkout-readiness.js +17 -0
- package/dist/sdk/address/checkout-readiness.js.map +1 -0
- package/dist/sdk/address/constants.js +60 -0
- package/dist/sdk/address/constants.js.map +1 -0
- package/dist/sdk/address/country-display.js +19 -0
- package/dist/sdk/address/country-display.js.map +1 -0
- package/dist/sdk/address/country-rules.js +30 -0
- package/dist/sdk/address/country-rules.js.map +1 -0
- package/dist/sdk/address/format-address.js +18 -0
- package/dist/sdk/address/format-address.js.map +1 -0
- package/dist/sdk/address/iso-countries.js +262 -0
- package/dist/sdk/address/iso-countries.js.map +1 -0
- package/dist/sdk/address/layouts.js +56 -0
- package/dist/sdk/address/layouts.js.map +1 -0
- package/dist/sdk/address/messages.js +97 -0
- package/dist/sdk/address/messages.js.map +1 -0
- package/dist/sdk/address/operations.d.ts +54 -0
- package/dist/sdk/address/operations.js +83 -0
- package/dist/sdk/address/operations.js.map +1 -0
- package/dist/sdk/address/postal-code-lookup-operations.js +23 -0
- package/dist/sdk/address/postal-code-lookup-operations.js.map +1 -0
- package/dist/sdk/address/postal-code.js +11 -0
- package/dist/sdk/address/postal-code.js.map +1 -0
- package/dist/sdk/address/schema.js +14 -0
- package/dist/sdk/address/schema.js.map +1 -0
- package/dist/sdk/address/server-fields.js +25 -0
- package/dist/sdk/address/server-fields.js.map +1 -0
- package/dist/sdk/address/switch-country.js +57 -0
- package/dist/sdk/address/switch-country.js.map +1 -0
- package/dist/sdk/address/types.d.ts +26 -0
- package/dist/sdk/address/use-address-actions.d.ts +29 -0
- package/dist/sdk/address/use-address-actions.js +247 -0
- package/dist/sdk/address/use-address-actions.js.map +1 -0
- package/dist/sdk/address/use-address-by-postal-code.js +57 -0
- package/dist/sdk/address/use-address-by-postal-code.js.map +1 -0
- package/dist/sdk/address/validate-address.js +62 -0
- package/dist/sdk/address/validate-address.js.map +1 -0
- package/dist/sdk/cache/storefront-cache.js +30 -4
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/cart/operations.d.ts +2 -1
- package/dist/sdk/cart/operations.js +55 -1
- package/dist/sdk/cart/operations.js.map +1 -1
- package/dist/sdk/cart/optimistic.d.ts +30 -7
- package/dist/sdk/cart/optimistic.js +78 -41
- package/dist/sdk/cart/optimistic.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.d.ts +7 -0
- package/dist/sdk/cart/use-cart-actions.js +61 -15
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-sync-sales-channel.js +70 -0
- package/dist/sdk/cart/use-cart-sync-sales-channel.js.map +1 -0
- package/dist/sdk/client/fetch-page-data.d.ts +11 -0
- package/dist/sdk/client/fetch-page-data.js +12 -1
- package/dist/sdk/client/fetch-page-data.js.map +1 -1
- package/dist/sdk/content/create-content-runtime.js +1 -8
- package/dist/sdk/content/create-content-runtime.js.map +1 -1
- package/dist/sdk/data/failed-fields.js +23 -0
- package/dist/sdk/data/failed-fields.js.map +1 -0
- package/dist/sdk/data/index.js +2 -2
- package/dist/sdk/data/page-reactive-data-root.d.ts +45 -7
- package/dist/sdk/data/page-reactive-data-root.js +88 -12
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +2 -2
- 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 +17 -11
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.d.ts +185 -46
- package/dist/sdk/generated/graphql.js +2587 -155
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +157 -1
- package/dist/sdk/i18n/messages/en-US.json +157 -1
- package/dist/sdk/i18n/messages/en-US.json.d.ts +156 -0
- package/dist/sdk/i18n/messages/es-ES.js +157 -1
- package/dist/sdk/i18n/messages/es-ES.json +157 -1
- package/dist/sdk/i18n/messages/pt-BR.js +157 -1
- package/dist/sdk/i18n/messages/pt-BR.json +157 -1
- package/dist/sdk/i18n/provider.js +1 -1
- package/dist/sdk/i18n/provider.js.map +1 -1
- package/dist/sdk/locale/binding-switch-provider.d.ts +13 -0
- package/dist/sdk/locale/binding-switch-provider.js +20 -0
- package/dist/sdk/locale/binding-switch-provider.js.map +1 -0
- package/dist/sdk/locale/use-locale-actions.d.ts +16 -0
- package/dist/sdk/locale/use-locale-actions.js +70 -0
- package/dist/sdk/locale/use-locale-actions.js.map +1 -0
- package/dist/sdk/navigation/binding-switch.d.ts +39 -0
- package/dist/sdk/navigation/binding-switch.js +129 -0
- package/dist/sdk/navigation/binding-switch.js.map +1 -0
- package/dist/sdk/navigation/current-page.js +30 -0
- package/dist/sdk/navigation/current-page.js.map +1 -0
- package/dist/sdk/navigation/decode-route-params.d.ts +11 -0
- package/dist/sdk/navigation/decode-route-params.js +32 -0
- package/dist/sdk/navigation/decode-route-params.js.map +1 -0
- package/dist/sdk/navigation/not-found-log.d.ts +28 -0
- package/dist/sdk/navigation/not-found-log.js +133 -0
- package/dist/sdk/navigation/not-found-log.js.map +1 -0
- package/dist/sdk/navigation/path.js +25 -0
- package/dist/sdk/navigation/path.js.map +1 -0
- package/dist/sdk/navigation/redirect.d.ts +11 -0
- package/dist/sdk/navigation/redirect.js +22 -0
- package/dist/sdk/navigation/redirect.js.map +1 -0
- package/dist/sdk/navigation/report.js +70 -0
- package/dist/sdk/navigation/report.js.map +1 -0
- package/dist/sdk/navigation/resolve-storefront-href.d.ts +12 -0
- package/dist/sdk/navigation/resolve-storefront-href.js +214 -0
- package/dist/sdk/navigation/resolve-storefront-href.js.map +1 -0
- package/dist/sdk/navigation/route-identity-provider.d.ts +11 -0
- package/dist/sdk/navigation/route-identity-provider.js +20 -0
- package/dist/sdk/navigation/route-identity-provider.js.map +1 -0
- package/dist/sdk/navigation/route-map-provider.d.ts +19 -0
- package/dist/sdk/navigation/route-map-provider.js +29 -0
- package/dist/sdk/navigation/route-map-provider.js.map +1 -0
- package/dist/sdk/navigation/storefront-link.d.ts +16 -0
- package/dist/sdk/navigation/storefront-link.js +66 -0
- package/dist/sdk/navigation/storefront-link.js.map +1 -0
- package/dist/sdk/navigation/types.d.ts +45 -0
- package/dist/sdk/navigation/use-storefront-navigation.d.ts +26 -0
- package/dist/sdk/navigation/use-storefront-navigation.js +73 -0
- package/dist/sdk/navigation/use-storefront-navigation.js.map +1 -0
- package/dist/sdk/navigation/use-storefront-route.d.ts +26 -0
- package/dist/sdk/navigation/use-storefront-route.js +87 -0
- package/dist/sdk/navigation/use-storefront-route.js.map +1 -0
- package/dist/sdk/navigation/use-storefront-search-params.d.ts +11 -0
- package/dist/sdk/navigation/use-storefront-search-params.js +16 -0
- package/dist/sdk/navigation/use-storefront-search-params.js.map +1 -0
- package/dist/sdk/orders/completion-badge.d.ts +8 -0
- package/dist/sdk/orders/completion-badge.js +47 -0
- package/dist/sdk/orders/completion-badge.js.map +1 -0
- package/dist/sdk/orders/order-operations.d.ts +19 -0
- package/dist/sdk/orders/order-operations.js +21 -0
- package/dist/sdk/orders/order-operations.js.map +1 -0
- package/dist/sdk/orders/use-order-actions.d.ts +23 -0
- package/dist/sdk/orders/use-order-actions.js +62 -0
- package/dist/sdk/orders/use-order-actions.js.map +1 -0
- package/dist/sdk/payment/gateway-origin.js +26 -13
- package/dist/sdk/payment/gateway-origin.js.map +1 -1
- package/dist/sdk/payment/gateway.d.ts +14 -2
- package/dist/sdk/payment/gateway.js +32 -14
- package/dist/sdk/payment/gateway.js.map +1 -1
- package/dist/sdk/payment/operations.js +11 -0
- package/dist/sdk/payment/operations.js.map +1 -1
- package/dist/sdk/payment/submission-registry.d.ts +2 -1
- package/dist/sdk/payment/submission-registry.js +12 -8
- package/dist/sdk/payment/submission-registry.js.map +1 -1
- package/dist/sdk/payment/use-payment-actions.d.ts +5 -3
- package/dist/sdk/payment/use-payment-actions.js +5 -2
- package/dist/sdk/payment/use-payment-actions.js.map +1 -1
- package/dist/sdk/payment/use-place-order.d.ts +6 -3
- package/dist/sdk/payment/use-place-order.js +333 -143
- package/dist/sdk/payment/use-place-order.js.map +1 -1
- package/dist/sdk/saved-cards/operations.d.ts +16 -0
- package/dist/sdk/saved-cards/operations.js +41 -0
- package/dist/sdk/saved-cards/operations.js.map +1 -0
- package/dist/sdk/saved-cards/use-saved-card-actions.d.ts +26 -0
- package/dist/sdk/saved-cards/use-saved-card-actions.js +86 -0
- package/dist/sdk/saved-cards/use-saved-card-actions.js.map +1 -0
- package/dist/sdk/seo/page-alternates.d.ts +57 -0
- package/dist/sdk/seo/page-alternates.js +127 -0
- package/dist/sdk/seo/page-alternates.js.map +1 -0
- package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
- package/dist/sdk/shipping/shipping-operations.js +13 -0
- package/dist/sdk/shipping/shipping-operations.js.map +1 -1
- package/dist/sdk/state/cart.d.ts +9 -2
- package/dist/sdk/state/cart.js +9 -2
- package/dist/sdk/state/cart.js.map +1 -1
- package/dist/utils/own-value.js +19 -0
- package/dist/utils/own-value.js.map +1 -0
- package/dist/utils/safe-href.d.ts +4 -4
- package/dist/utils/safe-href.js +31 -22
- package/dist/utils/safe-href.js.map +1 -1
- package/package.json +5 -1
- package/skills/storefront-asset/SKILL.md +111 -0
- package/skills/storefront-asset/references/api-side.md +153 -0
- package/skills/storefront-asset/references/findings.md +40 -0
- package/skills/storefront-asset/references/ui-side.md +94 -0
- package/src/styles/tailwind.css +28 -2
package/dist/cli/index.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
+
import { chmodSync, closeSync, copyFileSync, cpSync, existsSync, linkSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync, writeSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { kill, pid } from "node:process";
|
|
5
6
|
import { Kind, parse, print, visit } from "graphql";
|
|
6
|
-
import { pid } from "node:process";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
+
import { createInterface } from "node:readline";
|
|
9
10
|
import crypto from "crypto";
|
|
10
11
|
//#region \0rolldown/runtime.js
|
|
11
|
-
var __create = Object.create;
|
|
12
12
|
var __defProp = Object.defineProperty;
|
|
13
13
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
14
14
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
15
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
16
15
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
17
16
|
var __esmMin = (fn, res, err) => () => {
|
|
18
17
|
if (err) throw err[0];
|
|
@@ -42,13 +41,27 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
42
41
|
}
|
|
43
42
|
return to;
|
|
44
43
|
};
|
|
45
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
46
|
-
value: mod,
|
|
47
|
-
enumerable: true
|
|
48
|
-
}) : target, mod));
|
|
49
44
|
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
50
45
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
51
46
|
//#endregion
|
|
47
|
+
//#region ../../node_modules/.pnpm/@vtex+extension-cli-kit@0.1.1/node_modules/@vtex/extension-cli-kit/dist/kinds.js
|
|
48
|
+
/** RFC-0001's closed set of kinds. A new kind arrives by its own RFC. */
|
|
49
|
+
const kinds = [
|
|
50
|
+
"storefront",
|
|
51
|
+
"widget",
|
|
52
|
+
"instructions",
|
|
53
|
+
"workflow",
|
|
54
|
+
"agent",
|
|
55
|
+
"function"
|
|
56
|
+
];
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region ../../node_modules/.pnpm/@vtex+extension-cli-kit@0.1.1/node_modules/@vtex/extension-cli-kit/dist/provider.js
|
|
59
|
+
/**
|
|
60
|
+
* The kind provider contract version this kit describes. A provider declares the version it
|
|
61
|
+
* implements as `contract`; the CLI checks it against the range it supports.
|
|
62
|
+
*/
|
|
63
|
+
const providerContractVersion = 0;
|
|
64
|
+
//#endregion
|
|
52
65
|
//#region ../graphql-compose/dist/subgraph-names.js
|
|
53
66
|
var require_subgraph_names = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
54
67
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -12080,7 +12093,7 @@ var require_has_flag = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
12080
12093
|
//#endregion
|
|
12081
12094
|
//#region ../../node_modules/.pnpm/supports-color@8.1.1/node_modules/supports-color/index.js
|
|
12082
12095
|
var require_supports_color$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
12083
|
-
const os$
|
|
12096
|
+
const os$1 = __require("os");
|
|
12084
12097
|
const tty$2 = __require("tty");
|
|
12085
12098
|
const hasFlag = require_has_flag();
|
|
12086
12099
|
const { env } = process;
|
|
@@ -12116,7 +12129,7 @@ var require_supports_color$1 = /* @__PURE__ */ __commonJSMin(((exports, module)
|
|
|
12116
12129
|
const min = forceColor || 0;
|
|
12117
12130
|
if (env.TERM === "dumb") return min;
|
|
12118
12131
|
if (process.platform === "win32") {
|
|
12119
|
-
const osRelease = os$
|
|
12132
|
+
const osRelease = os$1.release().split(".");
|
|
12120
12133
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
12121
12134
|
return 1;
|
|
12122
12135
|
}
|
|
@@ -17703,12 +17716,254 @@ var require_compose = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
17703
17716
|
}
|
|
17704
17717
|
}));
|
|
17705
17718
|
//#endregion
|
|
17706
|
-
//#region src/cli/
|
|
17719
|
+
//#region src/cli/paths.ts
|
|
17707
17720
|
var import_subgraph_names = require_subgraph_names();
|
|
17708
17721
|
var import_compose = require_compose();
|
|
17709
|
-
/**
|
|
17710
|
-
|
|
17711
|
-
|
|
17722
|
+
/**
|
|
17723
|
+
* `path` as the filesystem spells it: symlinks followed and the on-disk case
|
|
17724
|
+
* restored through the nearest ancestor that exists, with the rest joined
|
|
17725
|
+
* back. Compared as strings, `../Ext` is a sibling of the Asset at `ext` on a
|
|
17726
|
+
* case-insensitive filesystem, and a symlink to the Asset is a sibling
|
|
17727
|
+
* anywhere; both name the Asset.
|
|
17728
|
+
*/
|
|
17729
|
+
function canonical(path) {
|
|
17730
|
+
const missing = [];
|
|
17731
|
+
let existing = path;
|
|
17732
|
+
while (!existsSync(existing)) {
|
|
17733
|
+
missing.unshift(basename(existing));
|
|
17734
|
+
const parent = dirname(existing);
|
|
17735
|
+
if (parent === existing) break;
|
|
17736
|
+
existing = parent;
|
|
17737
|
+
}
|
|
17738
|
+
return join(realpathSync.native(existing), ...missing);
|
|
17739
|
+
}
|
|
17740
|
+
/** True when `path` is `root` or lies under it, as the filesystem spells both. */
|
|
17741
|
+
function isInside(path, root) {
|
|
17742
|
+
const rel = relative(canonical(root), canonical(path));
|
|
17743
|
+
return rel === "" || !isAbsolute(rel) && rel.split(sep)[0] !== "..";
|
|
17744
|
+
}
|
|
17745
|
+
//#endregion
|
|
17746
|
+
//#region src/cli/asset-layout.ts
|
|
17747
|
+
/** The layout of the Asset at `root`: its `package.json` parsed when present, absent otherwise. */
|
|
17748
|
+
function readAssetLayout(root) {
|
|
17749
|
+
const packageJsonPath = join(root, "package.json");
|
|
17750
|
+
if (!existsSync(packageJsonPath)) return { dir: root };
|
|
17751
|
+
return {
|
|
17752
|
+
dir: root,
|
|
17753
|
+
packageJson: JSON.parse(readFileSync(packageJsonPath, "utf-8"))
|
|
17754
|
+
};
|
|
17755
|
+
}
|
|
17756
|
+
/** True when `package.json#exports` has the `./catalog.json` key: the one UI signal (ADR-0007). */
|
|
17757
|
+
function exportsCatalog(asset) {
|
|
17758
|
+
const { exports } = asset.packageJson ?? {};
|
|
17759
|
+
return typeof exports === "object" && exports !== null && "./catalog.json" in exports;
|
|
17760
|
+
}
|
|
17761
|
+
/**
|
|
17762
|
+
* The conditions a build-time reader takes first inside a conditions object, in this order,
|
|
17763
|
+
* before any other key: `types` may name a `.d.ts`, and key order is the author's, not a
|
|
17764
|
+
* preference. Sukima's `export_target` (`store_project.rs`) reads the same way, so the provider
|
|
17765
|
+
* writes the catalog where the store reads it.
|
|
17766
|
+
*/
|
|
17767
|
+
const PREFERRED_CONDITIONS = [
|
|
17768
|
+
"default",
|
|
17769
|
+
"import",
|
|
17770
|
+
"node",
|
|
17771
|
+
"require"
|
|
17772
|
+
];
|
|
17773
|
+
function catalogExport(asset) {
|
|
17774
|
+
const { exports } = asset.packageJson ?? {};
|
|
17775
|
+
if (!exportsCatalog(asset)) return { kind: "none" };
|
|
17776
|
+
const target = exportTarget(exports["./catalog.json"]);
|
|
17777
|
+
if (target === void 0) return {
|
|
17778
|
+
kind: "invalid",
|
|
17779
|
+
reason: "names no file"
|
|
17780
|
+
};
|
|
17781
|
+
if (isAbsolute(target)) return {
|
|
17782
|
+
kind: "invalid",
|
|
17783
|
+
reason: `names "${target}", which is absolute`
|
|
17784
|
+
};
|
|
17785
|
+
if (target.split("/").includes("..")) return {
|
|
17786
|
+
kind: "invalid",
|
|
17787
|
+
reason: `names "${target}", which leaves the Asset folder`
|
|
17788
|
+
};
|
|
17789
|
+
if (!target.startsWith("./")) return {
|
|
17790
|
+
kind: "invalid",
|
|
17791
|
+
reason: `names "${target}", which does not start with "./"`
|
|
17792
|
+
};
|
|
17793
|
+
const file = target.slice(target.lastIndexOf("/") + 1);
|
|
17794
|
+
if (file === "" || file === ".") return {
|
|
17795
|
+
kind: "invalid",
|
|
17796
|
+
reason: `names "${target}", which names a folder, not a file`
|
|
17797
|
+
};
|
|
17798
|
+
if (target.split("/").includes("")) return {
|
|
17799
|
+
kind: "invalid",
|
|
17800
|
+
reason: `names "${target}", which has an empty segment`
|
|
17801
|
+
};
|
|
17802
|
+
if (file !== "catalog.json") return {
|
|
17803
|
+
kind: "invalid",
|
|
17804
|
+
reason: `names "${target}", whose file is not catalog.json, the file the build writes`
|
|
17805
|
+
};
|
|
17806
|
+
if (!isInside(join(asset.dir, target), asset.dir)) return {
|
|
17807
|
+
kind: "invalid",
|
|
17808
|
+
reason: `names "${target}", which resolves outside the Asset folder through a link`
|
|
17809
|
+
};
|
|
17810
|
+
return {
|
|
17811
|
+
kind: "target",
|
|
17812
|
+
target
|
|
17813
|
+
};
|
|
17814
|
+
}
|
|
17815
|
+
/** The usable subpath `exports["./catalog.json"]` names, relative to the Asset, or `undefined`. */
|
|
17816
|
+
function catalogExportTarget(asset) {
|
|
17817
|
+
const exported = catalogExport(asset);
|
|
17818
|
+
return exported.kind === "target" ? exported.target : void 0;
|
|
17819
|
+
}
|
|
17820
|
+
/**
|
|
17821
|
+
* The file an `exports` entry names: the string itself; inside a conditions object the preferred
|
|
17822
|
+
* condition that names one, else the first key that does; the first string of a fallback array.
|
|
17823
|
+
*/
|
|
17824
|
+
function exportTarget(value) {
|
|
17825
|
+
if (typeof value === "string") return value;
|
|
17826
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
17827
|
+
const entries = Array.isArray(value) ? value : [...PREFERRED_CONDITIONS.filter((condition) => condition in value).map((condition) => value[condition]), ...Object.values(value)];
|
|
17828
|
+
for (const entry of entries) {
|
|
17829
|
+
const target = exportTarget(entry);
|
|
17830
|
+
if (target !== void 0) return target;
|
|
17831
|
+
}
|
|
17832
|
+
}
|
|
17833
|
+
/**
|
|
17834
|
+
* Where the Asset's catalog goes so a store finds it: the folder of the file its
|
|
17835
|
+
* `exports["./catalog.json"]` names (ADR-0007), or `dist/` when the Asset has no usable export,
|
|
17836
|
+
* which `validate` reports. `dev` builds there on every save, and `build` copies its catalog there
|
|
17837
|
+
* beside the copy in `outDir`.
|
|
17838
|
+
*/
|
|
17839
|
+
function catalogOutDir(asset) {
|
|
17840
|
+
const target = catalogExportTarget(asset);
|
|
17841
|
+
return target === void 0 ? join(asset.dir, "dist") : dirname(join(asset.dir, target));
|
|
17842
|
+
}
|
|
17843
|
+
/**
|
|
17844
|
+
* The declaration entry of the Asset's API Extension, absolute, or `undefined` when the Asset has
|
|
17845
|
+
* none. `resolveDeclarationEntry`'s convention — `package.json#main`, else `src/index.ts` — plus the
|
|
17846
|
+
* one UI signal: an Asset that exports `./catalog.json` is a UI Extension, so its `src/index.ts` is
|
|
17847
|
+
* the component barrel, never a declaration. A dual Asset names its declaration through `main`.
|
|
17848
|
+
*/
|
|
17849
|
+
function apiDeclarationEntry(asset) {
|
|
17850
|
+
const { main } = asset.packageJson ?? {};
|
|
17851
|
+
if (typeof main === "string") return join(asset.dir, main);
|
|
17852
|
+
const conventional = join(asset.dir, "src", "index.ts");
|
|
17853
|
+
return existsSync(conventional) && !exportsCatalog(asset) ? conventional : void 0;
|
|
17854
|
+
}
|
|
17855
|
+
/**
|
|
17856
|
+
* True when the Asset has a UI Extension: a `src/components/` directory (the layout the catalog
|
|
17857
|
+
* build scans for an extension) or the `./catalog.json` export. Either alone counts, so both
|
|
17858
|
+
* failure modes are reported: a UI side that forgot the export, and an export that promises a
|
|
17859
|
+
* catalog no components can build.
|
|
17860
|
+
*/
|
|
17861
|
+
function uiSide(asset) {
|
|
17862
|
+
return statSync(join(asset.dir, "src", "components"), { throwIfNoEntry: false })?.isDirectory() === true || exportsCatalog(asset);
|
|
17863
|
+
}
|
|
17864
|
+
//#endregion
|
|
17865
|
+
//#region src/cli/bundler.ts
|
|
17866
|
+
/**
|
|
17867
|
+
* esbuild, imported on first use. It is a regular dependency of
|
|
17868
|
+
* `@vtex/faststore`, pinned to one minor (ADR-0026): the runner bundles with it
|
|
17869
|
+
* to load an extension's declaration (`validate`, `build`, `dev`) and to emit
|
|
17870
|
+
* `server.mjs` (`build`). The import is lazy so `dist/cli/index.js` loads where
|
|
17871
|
+
* esbuild's platform binary is not installed, and so the provider's members
|
|
17872
|
+
* that bundle nothing never pay for it.
|
|
17873
|
+
*/
|
|
17874
|
+
async function loadBundler(verb) {
|
|
17875
|
+
try {
|
|
17876
|
+
const mod = await import("esbuild");
|
|
17877
|
+
return mod.default ?? mod;
|
|
17878
|
+
} catch (error) {
|
|
17879
|
+
throw new Error(`storefront ${verb}: could not load esbuild, a dependency of @vtex/faststore — reinstall the package. Cause: ${error instanceof Error ? error.message : String(error)}`);
|
|
17880
|
+
}
|
|
17881
|
+
}
|
|
17882
|
+
//#endregion
|
|
17883
|
+
//#region src/cli/catalog.ts
|
|
17884
|
+
/**
|
|
17885
|
+
* The catalog build addon, imported on first use. `@vtex/faststore-catalog` is
|
|
17886
|
+
* a regular dependency of `@vtex/faststore` (ADR-0026): the runner builds a
|
|
17887
|
+
* UI Extension's catalog with it (`validate`, `dev`, `build`). The import is
|
|
17888
|
+
* lazy because the addon loads its native binary at require time, and the
|
|
17889
|
+
* publish smoke installs with `--omit=optional`, which drops the platform
|
|
17890
|
+
* package: a static import would make `dist/cli/index.js` fail to load there.
|
|
17891
|
+
*/
|
|
17892
|
+
async function loadCatalogBuilder(verb) {
|
|
17893
|
+
try {
|
|
17894
|
+
const mod = await import("@vtex/faststore-catalog");
|
|
17895
|
+
return mod.default ?? mod;
|
|
17896
|
+
} catch (error) {
|
|
17897
|
+
throw new Error(`storefront ${verb}: could not load @vtex/faststore-catalog, a dependency of @vtex/faststore — reinstall the package. Cause: ${error instanceof Error ? error.message : String(error)}`);
|
|
17898
|
+
}
|
|
17899
|
+
}
|
|
17900
|
+
/**
|
|
17901
|
+
* Builds the catalog of the Asset's UI Extension at `root` into `outDir`:
|
|
17902
|
+
* `src/components/` scanned, keys namespaced by `package.json#name`. Always
|
|
17903
|
+
* `kind: 'extension'`, because the provider knows which side it is building
|
|
17904
|
+
* (ADR-0007): an Asset named `@vtex/faststore` fails with `PackageKindMismatch`
|
|
17905
|
+
* instead of building as the Core package. No fragment map yet: a component
|
|
17906
|
+
* that spreads a fragment fails with `UnresolvedSpread`.
|
|
17907
|
+
*/
|
|
17908
|
+
async function buildAssetCatalog(root, outDir, verb) {
|
|
17909
|
+
const { buildPackageCatalog } = await loadCatalogBuilder(verb);
|
|
17910
|
+
return buildPackageCatalog({
|
|
17911
|
+
packageDir: root,
|
|
17912
|
+
kind: "extension",
|
|
17913
|
+
outDir
|
|
17914
|
+
});
|
|
17915
|
+
}
|
|
17916
|
+
//#endregion
|
|
17917
|
+
//#region src/cli/component-docs.ts
|
|
17918
|
+
/**
|
|
17919
|
+
* Scans the UI side's `src/components/` tree for `*.md` and plans one flat
|
|
17920
|
+
* copy per doc, sorted by destination. The runner's port of
|
|
17921
|
+
* `scripts/build-docs.mjs`, which does the same for the Core package's two
|
|
17922
|
+
* trees.
|
|
17923
|
+
*
|
|
17924
|
+
* The shipped filename is the source basename. The catalog's `docPath` is
|
|
17925
|
+
* package-relative and names the `src/` file (`<stem>.md` beside the
|
|
17926
|
+
* component, or an explicit `docPath` relative to it), and `src/` never
|
|
17927
|
+
* ships, so a reader resolves `docs/<basename(docPath)>` under the output
|
|
17928
|
+
* folder — the convention `@vtex/faststore`'s own `dist/docs/` follows
|
|
17929
|
+
* (ADR-0019). That mapping only holds while basenames are unique, so a
|
|
17930
|
+
* collision fails the build instead of shipping whichever file was copied
|
|
17931
|
+
* last. A missing tree plans nothing: the UI side may be an export alone,
|
|
17932
|
+
* which the catalog build then rejects on its own terms.
|
|
17933
|
+
*/
|
|
17934
|
+
function planDocCopies(root) {
|
|
17935
|
+
const componentsDir = join(root, "src", "components");
|
|
17936
|
+
if (!existsSync(componentsDir)) return [];
|
|
17937
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
17938
|
+
const copies = [];
|
|
17939
|
+
for (const entry of readdirSync(componentsDir, {
|
|
17940
|
+
withFileTypes: true,
|
|
17941
|
+
recursive: true
|
|
17942
|
+
})) {
|
|
17943
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
17944
|
+
const source = join(entry.parentPath, entry.name);
|
|
17945
|
+
const previous = claimed.get(entry.name);
|
|
17946
|
+
if (previous) {
|
|
17947
|
+
const [a, b] = [previous, source].map((path) => relative(root, path)).sort((x, y) => x.localeCompare(y));
|
|
17948
|
+
throw new Error(`storefront build: component doc basename collision: "${entry.name}" is claimed by both ${a} and ${b}. Shipped docs are flat, and the catalog maps an entry to its doc by basename — rename one of them.`);
|
|
17949
|
+
}
|
|
17950
|
+
claimed.set(entry.name, source);
|
|
17951
|
+
copies.push({
|
|
17952
|
+
source,
|
|
17953
|
+
destination: entry.name
|
|
17954
|
+
});
|
|
17955
|
+
}
|
|
17956
|
+
copies.sort((a, b) => a.destination.localeCompare(b.destination));
|
|
17957
|
+
return copies;
|
|
17958
|
+
}
|
|
17959
|
+
/**
|
|
17960
|
+
* Copies each planned doc into `docsDir` and returns the count. The folder is
|
|
17961
|
+
* created even for an empty plan, so a UI side's output always has `docs/`.
|
|
17962
|
+
*/
|
|
17963
|
+
function copyDocs(copies, docsDir) {
|
|
17964
|
+
mkdirSync(docsDir, { recursive: true });
|
|
17965
|
+
for (const { source, destination } of copies) copyFileSync(source, join(docsDir, destination));
|
|
17966
|
+
return copies.length;
|
|
17712
17967
|
}
|
|
17713
17968
|
/**
|
|
17714
17969
|
* SHA-256 of each release asset, keyed `<version>/<asset>`. Every supported
|
|
@@ -17756,6 +18011,11 @@ function verifyChecksum(version, asset, contents) {
|
|
|
17756
18011
|
* and marks it executable. Written through a sibling temp file and renamed, so
|
|
17757
18012
|
* an interrupted download never leaves a truncated binary in the cache for the
|
|
17758
18013
|
* next run to execute.
|
|
18014
|
+
*
|
|
18015
|
+
* The temp name carries the pid because two processes can miss one cache at
|
|
18016
|
+
* once — two `dev` sessions, or two e2e files in parallel workers. On a shared
|
|
18017
|
+
* name the second rename finds no file (ENOENT), and the first one's cleanup
|
|
18018
|
+
* deletes the second's pending write.
|
|
17759
18019
|
*/
|
|
17760
18020
|
async function downloadRouterBinary(binaryPath, version) {
|
|
17761
18021
|
const asset = routerAssetName();
|
|
@@ -17770,7 +18030,7 @@ async function downloadRouterBinary(binaryPath, version) {
|
|
|
17770
18030
|
const contents = Buffer.from(await response.arrayBuffer());
|
|
17771
18031
|
verifyChecksum(version, asset, contents);
|
|
17772
18032
|
mkdirSync(dirname(binaryPath), { recursive: true });
|
|
17773
|
-
const partialPath = `${binaryPath}.partial`;
|
|
18033
|
+
const partialPath = `${binaryPath}.${pid}.partial`;
|
|
17774
18034
|
try {
|
|
17775
18035
|
writeFileSync(partialPath, contents);
|
|
17776
18036
|
chmodSync(partialPath, 493);
|
|
@@ -17780,16 +18040,15 @@ async function downloadRouterBinary(binaryPath, version) {
|
|
|
17780
18040
|
}
|
|
17781
18041
|
}
|
|
17782
18042
|
/**
|
|
17783
|
-
* Resolves the router binary in three steps: the
|
|
17784
|
-
*
|
|
17785
|
-
* fetched this version, then a download of the pinned
|
|
17786
|
-
* the path to run.
|
|
18043
|
+
* Resolves the router binary in three steps: `override` (the host's
|
|
18044
|
+
* `VTEX_STOREFRONT_ROUTER_BINARY`, used verbatim), then `cachedBinaryPath` if a
|
|
18045
|
+
* previous run already fetched this version, then a download of the pinned
|
|
18046
|
+
* release asset. Returns the path to run.
|
|
17787
18047
|
*
|
|
17788
18048
|
* The override is what makes air-gapped machines, CI caches and locally built
|
|
17789
18049
|
* routers all work without a special case each.
|
|
17790
18050
|
*/
|
|
17791
|
-
async function ensureRouterBinary(cachedBinaryPath, version
|
|
17792
|
-
const override = process.env.VTEX_STOREFRONT_ROUTER_BINARY?.trim();
|
|
18051
|
+
async function ensureRouterBinary(cachedBinaryPath, version, override) {
|
|
17793
18052
|
if (override) {
|
|
17794
18053
|
if (!existsSync(override)) throw new Error(`storefront dev: VTEX_STOREFRONT_ROUTER_BINARY points at ${override}, which does not exist.`);
|
|
17795
18054
|
return override;
|
|
@@ -17798,19 +18057,32 @@ async function ensureRouterBinary(cachedBinaryPath, version = routerVersion()) {
|
|
|
17798
18057
|
await downloadRouterBinary(cachedBinaryPath, version);
|
|
17799
18058
|
return cachedBinaryPath;
|
|
17800
18059
|
}
|
|
18060
|
+
/** Forwards each line of a child stream to `sink`. A null stream (no pipe) forwards nothing. */
|
|
18061
|
+
function forwardLines(stream, sink) {
|
|
18062
|
+
if (!stream) return;
|
|
18063
|
+
createInterface({ input: stream }).on("line", sink);
|
|
18064
|
+
}
|
|
17801
18065
|
/**
|
|
17802
18066
|
* Launches the Hive Router with the generated config. Resolves once the
|
|
17803
18067
|
* process has actually spawned, and rejects on `ENOENT`/`EACCES` so the
|
|
17804
18068
|
* caller can roll the subgraph back instead of racing an uncaught `error`.
|
|
17805
18069
|
* The binary takes no arguments: it reads `ROUTER_CONFIG_FILE_PATH`, and the
|
|
17806
18070
|
* listen address comes from the config's `http.host`/`http.port`.
|
|
18071
|
+
*
|
|
18072
|
+
* Output is piped, not inherited: every line reaches the host through the log
|
|
18073
|
+
* sink, so the runner writes nothing to its process's stdio (ADR-0026). Both
|
|
18074
|
+
* readers attach before `resolve`, so an unread pipe never blocks the child.
|
|
17807
18075
|
*/
|
|
17808
|
-
function spawnRouter(binaryPath, configPath) {
|
|
18076
|
+
function spawnRouter(binaryPath, configPath, { env, log }) {
|
|
17809
18077
|
return new Promise((resolve, reject) => {
|
|
17810
18078
|
const child = spawn(binaryPath, [], {
|
|
17811
|
-
stdio:
|
|
18079
|
+
stdio: [
|
|
18080
|
+
"ignore",
|
|
18081
|
+
"pipe",
|
|
18082
|
+
"pipe"
|
|
18083
|
+
],
|
|
17812
18084
|
env: {
|
|
17813
|
-
...
|
|
18085
|
+
...env,
|
|
17814
18086
|
ROUTER_CONFIG_FILE_PATH: configPath
|
|
17815
18087
|
}
|
|
17816
18088
|
});
|
|
@@ -17818,18 +18090,105 @@ function spawnRouter(binaryPath, configPath) {
|
|
|
17818
18090
|
child.once("spawn", () => {
|
|
17819
18091
|
child.removeListener("error", reject);
|
|
17820
18092
|
child.on("error", (error) => {
|
|
17821
|
-
|
|
18093
|
+
log.error(`storefront dev: Hive Router process error: ${error.message}`);
|
|
17822
18094
|
});
|
|
18095
|
+
forwardLines(child.stdout, (line) => log.info(line));
|
|
18096
|
+
forwardLines(child.stderr, (line) => log.error(line));
|
|
17823
18097
|
resolve(child);
|
|
17824
18098
|
});
|
|
17825
18099
|
});
|
|
17826
18100
|
}
|
|
17827
18101
|
/**
|
|
18102
|
+
* How long `startRouter` waits for the router to answer at its URL after the
|
|
18103
|
+
* spawn. The binary loads the supergraph and binds its port in well under a
|
|
18104
|
+
* second; a router still silent at the deadline is treated as down.
|
|
18105
|
+
*/
|
|
18106
|
+
const ROUTER_LISTEN_TIMEOUT_MS = 3e4;
|
|
18107
|
+
/** How often the readiness probe knocks on the router's URL. */
|
|
18108
|
+
const ROUTER_PROBE_INTERVAL_MS = 100;
|
|
18109
|
+
/**
|
|
18110
|
+
* How long one probe waits for an answer before it counts as "not yet". A server that accepts the
|
|
18111
|
+
* connection and never answers would otherwise hold the probe, and the pre-spawn check, forever.
|
|
18112
|
+
*/
|
|
18113
|
+
const ROUTER_PROBE_TIMEOUT_MS = 2e3;
|
|
18114
|
+
/** `with code 1`, or `on signal SIGKILL`. */
|
|
18115
|
+
function describeRouterExit({ code, signal }) {
|
|
18116
|
+
return code === null ? `on signal ${signal}` : `with code ${code}`;
|
|
18117
|
+
}
|
|
18118
|
+
/**
|
|
18119
|
+
* True when something answers a GraphQL request at `url` within `timeoutMs`. A transport error, a
|
|
18120
|
+
* stalled connection included, is "not yet" for the readiness probe; before the spawn, a `true`
|
|
18121
|
+
* means the port is taken by a server that answers GraphQL, a router a dead session left behind,
|
|
18122
|
+
* say, and the new router would lose the bind.
|
|
18123
|
+
*/
|
|
18124
|
+
async function routerAnswersAt(url, timeoutMs = ROUTER_PROBE_TIMEOUT_MS) {
|
|
18125
|
+
try {
|
|
18126
|
+
return (await fetch(url, {
|
|
18127
|
+
method: "POST",
|
|
18128
|
+
headers: { "content-type": "application/json" },
|
|
18129
|
+
body: JSON.stringify({ query: "{ __typename }" }),
|
|
18130
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
18131
|
+
})).ok;
|
|
18132
|
+
} catch {
|
|
18133
|
+
return false;
|
|
18134
|
+
}
|
|
18135
|
+
}
|
|
18136
|
+
/**
|
|
18137
|
+
* Resolves once the router answers at `url`. Rejects when the child exits
|
|
18138
|
+
* first or when `timeoutMs` elapses, naming the exit and the last lines the
|
|
18139
|
+
* router wrote to stderr (`recentStderr`) either way: a router that binds but
|
|
18140
|
+
* answers 5xx, over a supergraph it cannot load, say, explains itself there.
|
|
18141
|
+
* `spawn` alone proves nothing: a router whose port is taken spawns fine and
|
|
18142
|
+
* exits with `AddrInUse` a moment later, and a session that reports `ready` on
|
|
18143
|
+
* `spawn` hands out a URL nobody serves. The caller probes the URL once before
|
|
18144
|
+
* the spawn (`startRouter`), so an answer here is this child's.
|
|
18145
|
+
*/
|
|
18146
|
+
function waitForRouterListening(child, url, recentStderr, timeoutMs = ROUTER_LISTEN_TIMEOUT_MS) {
|
|
18147
|
+
return new Promise((resolve, reject) => {
|
|
18148
|
+
let settled = false;
|
|
18149
|
+
const settle = (outcome) => {
|
|
18150
|
+
if (settled) return;
|
|
18151
|
+
settled = true;
|
|
18152
|
+
child.removeListener("exit", onExit);
|
|
18153
|
+
clearTimeout(deadline);
|
|
18154
|
+
outcome();
|
|
18155
|
+
};
|
|
18156
|
+
const withTail = (message) => {
|
|
18157
|
+
const tail = recentStderr().map((line) => ` ${line}`);
|
|
18158
|
+
return tail.length > 0 ? `${message}\n${tail.join("\n")}` : message;
|
|
18159
|
+
};
|
|
18160
|
+
const onExit = (code, signal) => {
|
|
18161
|
+
settle(() => reject(new Error(withTail(`storefront dev: the Hive Router exited ${describeRouterExit({
|
|
18162
|
+
code,
|
|
18163
|
+
signal
|
|
18164
|
+
})} before it answered at ${url}.`))));
|
|
18165
|
+
};
|
|
18166
|
+
const deadline = setTimeout(() => {
|
|
18167
|
+
settle(() => reject(new Error(withTail(`storefront dev: the Hive Router did not answer at ${url} within ${timeoutMs}ms.`))));
|
|
18168
|
+
}, timeoutMs);
|
|
18169
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
18170
|
+
onExit(child.exitCode, child.signalCode);
|
|
18171
|
+
return;
|
|
18172
|
+
}
|
|
18173
|
+
child.once("exit", onExit);
|
|
18174
|
+
const probe = async () => {
|
|
18175
|
+
while (!settled) {
|
|
18176
|
+
if (await routerAnswersAt(url)) {
|
|
18177
|
+
settle(resolve);
|
|
18178
|
+
return;
|
|
18179
|
+
}
|
|
18180
|
+
await new Promise((tick) => setTimeout(tick, ROUTER_PROBE_INTERVAL_MS));
|
|
18181
|
+
}
|
|
18182
|
+
};
|
|
18183
|
+
probe();
|
|
18184
|
+
});
|
|
18185
|
+
}
|
|
18186
|
+
/**
|
|
17828
18187
|
* Where a cached router binary lives inside a scratch dir. Keyed by version,
|
|
17829
18188
|
* OS and arch so a copied scratch directory cannot execute the wrong target,
|
|
17830
18189
|
* and a version bump re-downloads.
|
|
17831
18190
|
*/
|
|
17832
|
-
function routerBinaryFileName(version
|
|
18191
|
+
function routerBinaryFileName(version, platform = process.platform, arch = process.arch) {
|
|
17833
18192
|
const os = ROUTER_OS[platform];
|
|
17834
18193
|
const cpu = ROUTER_ARCH[arch];
|
|
17835
18194
|
if (!os || !cpu) return `hive-router-${version}`;
|
|
@@ -17840,15 +18199,22 @@ function routerBinaryFileName(version = routerVersion(), platform = process.plat
|
|
|
17840
18199
|
/**
|
|
17841
18200
|
* The scratch directory's parent. `.vtex/` is shared with other VTEX tooling
|
|
17842
18201
|
* (a repository root may hold tracked deploy config there), so only the
|
|
17843
|
-
* `storefront/` folder inside it is the runner's, and only that is
|
|
18202
|
+
* `storefront/` folder inside it is the runner's, and only that is ignored.
|
|
17844
18203
|
*/
|
|
17845
18204
|
const SCRATCH_PARENT_DIR_NAME = ".vtex";
|
|
17846
18205
|
/** The runner's scratch folder under `.vtex/`: composed artifacts, router config and binary, bundled declarations. */
|
|
17847
18206
|
const SCRATCH_DIR_NAME = "storefront";
|
|
17848
|
-
/**
|
|
18207
|
+
/**
|
|
18208
|
+
* The scratch directory, `<root>/.vtex/storefront/`, created on first use with a `.gitignore`
|
|
18209
|
+
* that ignores everything in it. The folder ignores itself because not every root ignores it:
|
|
18210
|
+
* an Account Repo root's `.gitignore` lists `.vtex/build/` and `.vtex/dev-session.json` only,
|
|
18211
|
+
* and `dev` writes `router.config.yaml`, with `VTEX_TOKEN` in clear, under the session root.
|
|
18212
|
+
*/
|
|
17849
18213
|
function scratchDir(extensionRoot) {
|
|
17850
18214
|
const dir = join(extensionRoot, SCRATCH_PARENT_DIR_NAME, SCRATCH_DIR_NAME);
|
|
17851
18215
|
mkdirSync(dir, { recursive: true });
|
|
18216
|
+
const gitignore = join(dir, ".gitignore");
|
|
18217
|
+
if (!existsSync(gitignore)) writeFileSync(gitignore, "*\n", "utf-8");
|
|
17852
18218
|
return dir;
|
|
17853
18219
|
}
|
|
17854
18220
|
/** Where `loadDeclaration` writes the bundled declarations it imports. */
|
|
@@ -17857,6 +18223,12 @@ function loadDir(extensionRoot) {
|
|
|
17857
18223
|
mkdirSync(dir, { recursive: true });
|
|
17858
18224
|
return dir;
|
|
17859
18225
|
}
|
|
18226
|
+
/** Where `validate` builds a UI Extension's scratch catalog; `dev` and `build` pass their own `outDir`. */
|
|
18227
|
+
function catalogDir(extensionRoot) {
|
|
18228
|
+
const dir = join(scratchDir(extensionRoot), "catalog");
|
|
18229
|
+
mkdirSync(dir, { recursive: true });
|
|
18230
|
+
return dir;
|
|
18231
|
+
}
|
|
17860
18232
|
/**
|
|
17861
18233
|
* Where a cached Hive Router binary lives. Keyed by version, OS and arch, so
|
|
17862
18234
|
* bumping the pin fetches the new release and a copied scratch directory cannot
|
|
@@ -17916,13 +18288,13 @@ function formatCompositionErrors(verb, errors) {
|
|
|
17916
18288
|
* Callers pass a successful `composeSupergraph` result, so both files come from
|
|
17917
18289
|
* one composition and always describe the same graph.
|
|
17918
18290
|
*/
|
|
17919
|
-
function writeComposedArtifacts(extensionRoot, composed, verb) {
|
|
18291
|
+
function writeComposedArtifacts(extensionRoot, composed, verb, log) {
|
|
17920
18292
|
const supergraphFile = supergraphPath(extensionRoot);
|
|
17921
18293
|
writeFileAtomic(supergraphFile, composed.supergraphSdl);
|
|
17922
|
-
|
|
18294
|
+
log.info(`storefront ${verb}: composed ${supergraphFile} (${Buffer.byteLength(composed.supergraphSdl)} bytes of supergraph SDL).`);
|
|
17923
18295
|
const publicSchemaFile = publicSchemaPath(extensionRoot);
|
|
17924
18296
|
writeFileAtomic(publicSchemaFile, composed.publicSdl);
|
|
17925
|
-
|
|
18297
|
+
log.info(`storefront ${verb}: composed ${publicSchemaFile} (${Buffer.byteLength(composed.publicSdl)} bytes of client schema SDL).`);
|
|
17926
18298
|
return {
|
|
17927
18299
|
supergraphFile,
|
|
17928
18300
|
publicSchemaFile
|
|
@@ -17930,7 +18302,7 @@ function writeComposedArtifacts(extensionRoot, composed, verb) {
|
|
|
17930
18302
|
}
|
|
17931
18303
|
//#endregion
|
|
17932
18304
|
//#region ../../apps/storefront-api-core/schema/core.federation.graphql
|
|
17933
|
-
var core_federation_default = "# GENERATED by src/schema/emit.ts (pnpm run schema:emit) — do not edit by hand.\n# core version: 0.0.1\nschema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n mutation: Mutation\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.9\", import: [\"@key\"])\n\n\"\"\"\nRecords the planned removal date of a deprecated schema element. Pairs with the\nstandard built-in `@deprecated(reason)`: `@deprecated` carries the client-facing\nreason, `@deprecationDate` carries only the machine-readable deletion date. The\nreason is not duplicated here — that would drift from the built-in.\n\nThis replaces the previous redefinition of the built-in `@deprecated` (which\nadded a `deletionDate` argument). Redefining a built-in makes Federation\ncomposition (`wgc compose`) fail, so the deletion date moved to this custom\ndirective. As a non-built-in directive it is dropped during composition rather\nthan rejected. See ADR-00011 and CONTEXT.md. The `@deprecated`/`@deprecationDate`\npairing (both directions) and the future-date rule are enforced by a contract\ntest, not a lint rule.\n\"\"\"\ndirective @deprecationDate(\n \"\"\"\n Target removal date in ISO-8601 (YYYY-MM-DD) format. Must be in the future.\n \"\"\"\n deletionDate: String!\n) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE\n\n\"\"\"\nDeclares an HTTP cache policy for a type, field, or interface. Apollo Server\naggregates every hint resolved by a query and emits a single `Cache-Control`\nheader whose `max-age` is the minimum across hints and whose scope is the most\nrestrictive (PRIVATE wins over PUBLIC). Mutations and error responses ignore\nthese hints and always carry `Cache-Control: no-store`.\n\"\"\"\ndirective @cacheControl(\n \"\"\"Longest time (in seconds) this data may live in a cache.\"\"\"\n maxAge: Int\n\n \"\"\"\n Whether shared caches (CDN, proxy) may store the response. Defaults to\n PUBLIC when another field or type sets a scope and nothing overrides it.\n \"\"\"\n scope: CacheControlScope\n\n \"\"\"\n When true, this field inherits its `maxAge` from the parent field rather\n than contributing its own hint. Useful for wrapper/connection types.\n \"\"\"\n inheritMaxAge: Boolean\n) on OBJECT | FIELD_DEFINITION | INTERFACE\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!, label: String) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ndirective @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @federation__policy(policies: [[federation__Policy!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @federation__context(name: String!) repeatable on INTERFACE | OBJECT | UNION\n\ndirective @federation__fromContext(field: federation__ContextFieldValue) on ARGUMENT_DEFINITION\n\ndirective @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR\n\ndirective @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION\n\n\"\"\"Visibility scope for an HTTP cache entry.\"\"\"\nenum CacheControlScope {\n \"\"\"\n The response may be stored by shared caches (CDN, proxy) and reused\n across users.\n \"\"\"\n PUBLIC\n\n \"\"\"\n The response may only be stored by single-user caches (browser, service\n worker). Shared caches must not store it.\n \"\"\"\n PRIVATE\n}\n\n\"\"\"\nAn [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) encoded UTC datetime.\n\"\"\"\nscalar DateTime\n\n\"\"\"\nAn [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) encoded URI.\n\"\"\"\nscalar URI\n\n\"\"\"A string containing raw HTML markup.\"\"\"\nscalar HTML\n\n\"\"\"\nAn ISO 4217 currency amount represented as a decimal string (e.g. \"29.99\").\n\"\"\"\nscalar Decimal\n\n\"\"\"\nA [BCP 47](https://tools.ietf.org/html/bcp47) language tag (e.g. \"en-US\").\n\"\"\"\nscalar Locale\n\n\"\"\"An opaque cursor used for Relay-style pagination.\"\"\"\nscalar Cursor\n\n\"\"\"\nThe Relay `Node` interface. Every entity in the graph implements this so\nthe client can refetch any object with a single `node(id: ID!)` query.\n\"\"\"\ninterface Node {\n \"\"\"Globally unique identifier. Base64 of `entityType:rawId`.\"\"\"\n id: ID!\n}\n\n\"\"\"Metadata about the current page in a Relay connection.\"\"\"\ntype PageInfo {\n \"\"\"Whether more pages exist after the current page.\"\"\"\n hasNextPage: Boolean!\n\n \"\"\"Whether more pages exist before the current page.\"\"\"\n hasPreviousPage: Boolean!\n\n \"\"\"Cursor of the first edge in the current page.\"\"\"\n startCursor: Cursor\n\n \"\"\"Cursor of the last edge in the current page.\"\"\"\n endCursor: Cursor\n}\n\n\"\"\"Sort direction for orderBy arguments.\"\"\"\nenum SortDirection {\n \"\"\"Ascending order.\"\"\"\n ASC\n\n \"\"\"Descending order.\"\"\"\n DESC\n}\n\n\"\"\"Represents a monetary amount with its currency.\"\"\"\ntype Money {\n \"\"\"Decimal amount (e.g. \"49.99\").\"\"\"\n amount: Decimal!\n\n \"\"\"ISO 4217 currency code (e.g. \"USD\", \"BRL\").\"\"\"\n currencyCode: CurrencyCode!\n}\n\n\"\"\"A price range, useful for showing \"from – to\" on product cards.\"\"\"\ntype PriceRange {\n \"\"\"Lowest price across all variants.\"\"\"\n min: Money!\n\n \"\"\"Highest price across all variants.\"\"\"\n max: Money!\n}\n\n\"\"\"ISO 4217 currency codes. Extend as needed.\"\"\"\nenum CurrencyCode {\n \"\"\"United States Dollar.\"\"\"\n USD\n\n \"\"\"Euro.\"\"\"\n EUR\n\n \"\"\"British Pound Sterling.\"\"\"\n GBP\n\n \"\"\"Brazilian Real.\"\"\"\n BRL\n\n \"\"\"Argentine Peso.\"\"\"\n ARS\n\n \"\"\"Chilean Peso.\"\"\"\n CLP\n\n \"\"\"Colombian Peso.\"\"\"\n COP\n\n \"\"\"Mexican Peso.\"\"\"\n MXN\n\n \"\"\"Peruvian Sol.\"\"\"\n PEN\n\n \"\"\"Japanese Yen.\"\"\"\n JPY\n}\n\n\"\"\"An image resource associated with a product variant or other entity.\"\"\"\ntype Image {\n \"\"\"Source URL of the image.\"\"\"\n url: URI!\n\n \"\"\"Alt text for accessibility.\"\"\"\n altText: String\n}\n\n\"\"\"A video resource associated with a product.\"\"\"\ntype Video {\n \"\"\"Playback URL.\"\"\"\n url: URI!\n\n \"\"\"Alt text for accessibility.\"\"\"\n altText: String\n\n \"\"\"Preview / poster image.\"\"\"\n previewImage: Image\n}\n\n\"\"\"Any media attached to a product — image or video.\"\"\"\nunion Media = Image | Video\n\n\"\"\"Search engine optimization metadata for any displayable entity.\"\"\"\ntype SEO {\n \"\"\"Page title (`<title>` tag).\"\"\"\n title: String\n\n \"\"\"Meta description.\"\"\"\n description: String\n}\n\n\"\"\"Root query type for the storefront API.\"\"\"\ntype Query {\n \"\"\"\n Fetch any entity by its global ID.\n Clients use this for refetching or cache normalization.\n \"\"\"\n node(\n \"\"\"The globally unique ID.\"\"\"\n id: ID!\n ): Node\n\n \"\"\"Fetch multiple entities by their global IDs.\"\"\"\n nodes(\n \"\"\"A list of globally unique IDs.\"\"\"\n ids: [ID!]!\n ): [Node]!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nextend type Query {\n \"\"\"Look up a single product by global ID.\"\"\"\n product(\n \"\"\"Global product ID.\"\"\"\n id: ID!\n ): Product\n\n \"\"\"Look up a single product by its URL slug.\"\"\"\n productBySlug(\n \"\"\"URL-friendly handle (e.g. \\\"nike-zoom-janoski-red\\\").\"\"\"\n slug: String!\n ): Product\n\n \"\"\"Paginated, filterable list of products.\"\"\"\n products(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: ProductSortingInput\n\n \"\"\"Filter criteria.\"\"\"\n filter: ProductsFilterInput\n ): ProductsConnection!\n\n \"\"\"\n Full-text product search with facets and filters.\n Returns the same connection shape but is backed by the search engine\n rather than the catalog database.\n \"\"\"\n productSearch(\n \"\"\"Search query string.\"\"\"\n query: String!\n\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: ProductSortingInput\n\n \"\"\"Filter criteria.\"\"\"\n filter: ProductsFilterInput\n ): ProductSearchResult!\n\n \"\"\"\n Product recommendations based on a seed product (frequently bought\n together, similar items, etc.).\n \"\"\"\n productRecommendations(\n \"\"\"URL slug of the product to base recommendations on.\"\"\"\n slug: String!\n\n \"\"\"The recommendation strategy to use.\"\"\"\n intent: RecommendationIntent\n\n \"\"\"Maximum number of recommendations.\"\"\"\n first: Int = 10\n ): [Product!]!\n\n \"\"\"\n Resolves a catalog route by ID or by path and returns its confirmed localized equivalents.\n Null when the route does not exist or the catalog could not be reached.\n \"\"\"\n catalogRoute(\n \"\"\"The route to resolve, by ID or by path.\"\"\"\n input: CatalogRouteInput!\n ): CatalogRoute\n\n \"\"\"Look up a single category by global ID.\"\"\"\n category(\n \"\"\"Global category ID.\"\"\"\n id: ID!\n ): Category\n\n \"\"\"Look up a category by its URL slug.\"\"\"\n categoryBySlug(\n \"\"\"URL-friendly slug.\"\"\"\n slug: String!\n ): Category\n\n \"\"\"\n The full category tree starting from root departments.\n Accepts a `maxDepth` to limit tree traversal (default: all levels).\n \"\"\"\n categoryTree(\n \"\"\"Maximum depth of the tree to return. Maps to VTEX `categoryLevels`.\"\"\"\n maxDepth: Int\n ): [Category!]!\n\n \"\"\"Paginated flat list of categories.\"\"\"\n categories(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: CategorySortingInput\n ): CategoriesConnection!\n\n \"\"\"Look up a single brand by global ID.\"\"\"\n brand(\n \"\"\"Global brand ID.\"\"\"\n id: ID!\n ): Brand\n\n \"\"\"\n All brands available in the store, derived from the search index.\n Returns brands with their product counts.\n \"\"\"\n brands(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): BrandsConnection!\n\n \"\"\"Look up a single sales channel by global ID.\"\"\"\n salesChannel(\n \"\"\"Global sales channel ID.\"\"\"\n id: ID!\n ): SalesChannel\n\n \"\"\"\n Autocomplete suggestions for a partial search query.\n Returns suggested terms with estimated result counts and facet attributes.\n \"\"\"\n autocompleteSuggestions(\n \"\"\"Partial search query (e.g. \\\"choc\\\").\"\"\"\n query: String!\n ): [AutocompleteSuggestion!]!\n\n \"\"\"The most popular search terms in the last 14 days.\"\"\"\n topSearches: [TrendingSearch!]!\n\n \"\"\"\n Spelling correction for a potentially misspelled search query.\n Returns null if no correction is available.\n \"\"\"\n searchCorrection(\n \"\"\"The potentially misspelled query.\"\"\"\n query: String!\n ): SearchCorrection\n}\n\nextend type Query {\n \"\"\"\n Resolve the active cart from the request context (x-order-form-id header,\n then cookie). When no cart context is present, a new cart is created and\n the x-order-form-id cookie is set on the response.\n \"\"\"\n cart: Cart!\n}\n\nextend type Query {\n \"\"\"\n Simulate available shipping options for a variant on a product detail page.\n Returns delivery options with estimated times and prices for the given\n postal code and item.\n \"\"\"\n shippingSimulation(\n \"\"\"Simulation input: destination, SKU, quantity, and seller.\"\"\"\n input: ShippingSimulateInput!\n ): ShippingSimulationPayload!\n\n \"\"\"\n Simulate shipping options for the entire active cart. Reads the cart from the\n x-order-form-id context and simulates all of its items against the\n destination, returning the options common to every item with prices summed\n across items. Never mints a cart: absent context returns a MISSING_CART_CONTEXT\n userError.\n \"\"\"\n cartShippingSimulation(\n \"\"\"Simulation input: destination only. Items come from the active cart.\"\"\"\n input: CartShippingSimulateInput!\n ): CartShippingSimulationPayload!\n}\n\nextend type Query {\n \"\"\"\n The shopper's profile for the active cart, resolved from the same order form\n as `cart`. The entity always resolves; its fields are null when unset.\n \"\"\"\n clientProfile: ClientProfile!\n\n \"\"\"\n The shopper's preferences for the active cart, resolved from the same order\n form as `cart`. The entity always resolves; its fields are null when unset.\n \"\"\"\n clientPreferences: ClientPreferences!\n}\n\nextend type Query {\n \"\"\"\n The cart's shipping state. Projected from the same order form as Query.cart.\n \"\"\"\n shipping: Shipping!\n\n \"\"\"\n Resolve the shopper's session state from the active order form (x-order-form-id\n header, then cookie). Projects from the same order form fetch as `cart`.\n \"\"\"\n session: Session!\n}\n\nextend type Query {\n \"\"\"\n The cart's payment state: methods, selection, installment options, and saved cards. Projected from the same order form as Query.cart. Always resolves; lists are empty and selection fields are null when payment is unset.\n \"\"\"\n payment: Payment!\n}\n\nextend type Query {\n \"\"\"\n A settled order's payment recap, keyed on the order group\n paymentStartTransaction / paymentGatewayCallback return. Null when the\n order group does not resolve to a settled order — never a fabricated empty\n projection (contrast Query.payment, which always resolves).\n \"\"\"\n order(\n \"\"\"\n The order group returned by paymentStartTransaction / paymentGatewayCallback.\n \"\"\"\n orderGroup: String!\n ): Order\n}\n\nextend type Query {\n \"\"\"Which authentication methods this account enables.\"\"\"\n sessionAuthProviders: SessionAuthProviders!\n}\n\nextend type Query {\n \"\"\"\n The shopper's active region, or null when none has been set. Takes no\n arguments by design: the postal code travels on the transport.\n \"\"\"\n region: Region\n}\n\nextend type Query {\n \"\"\"\n The signed-in shopper's own persistent profile. Takes no id — the identity\n comes from the session. Nullable because it is permission-gated (ADR-00005).\n Null with no error when the session names no shopper. Null with an\n UNAUTHORIZED, UPSTREAM_ERROR, UPSTREAM_TIMEOUT or CIRCUIT_OPEN error on this\n field's path when the read fails (ADR-00007), so the rest of the operation\n still resolves. A profile whose fields are null when the shopper has saved\n nothing yet.\n \"\"\"\n customerProfile: CustomerProfile\n}\n\n\"\"\"\nRoot mutation type. Cart and checkout operations are declared via\n`extend type Mutation` in the checkout module.\n\"\"\"\ntype Mutation {\n \"\"\"\n Exists only so `extend type Mutation` in feature modules is valid for schema\n linters and subgraph composition; not intended for client use.\n \"\"\"\n inertMutationPlaceholder: Boolean\n\n \"\"\"Placeholder field — removed once a real mutation is added via extend.\"\"\"\n empty: String @deprecated(reason: \"Placeholder — will be dropped when the first real mutation is added.\") @deprecationDate(deletionDate: \"2026-12-31\")\n}\n\nextend type Mutation {\n \"\"\"\n Add an item to the cart. If the variant already exists in the cart, its\n quantity is increased by the given amount.\n \"\"\"\n cartItemAdd(\n \"\"\"Item details to add.\"\"\"\n input: CartItemAddInput!\n ): CartItemAddPayload!\n\n \"\"\"Remove an item from the cart entirely (sets quantity to 0).\"\"\"\n cartItemRemove(\n \"\"\"Item to remove.\"\"\"\n input: CartItemRemoveInput!\n ): CartItemRemovePayload!\n\n \"\"\"\n Remove multiple items from the cart in a single upstream write (each line's\n quantity is set to 0). Tolerant: variants absent from the cart are skipped.\n \"\"\"\n cartItemsRemove(\n \"\"\"Items to remove.\"\"\"\n input: CartItemsRemoveInput!\n ): CartItemsRemovePayload!\n\n \"\"\"Increase the quantity of an existing cart item.\"\"\"\n cartItemQuantityIncrease(\n \"\"\"Item and quantity details.\"\"\"\n input: CartItemQuantityIncreaseInput!\n ): CartItemQuantityIncreasePayload!\n\n \"\"\"\n Decrease the quantity of an existing cart item.\n If the resulting quantity would be zero or less, the item is removed.\n \"\"\"\n cartItemQuantityDecrease(\n \"\"\"Item and quantity details.\"\"\"\n input: CartItemQuantityDecreaseInput!\n ): CartItemQuantityDecreasePayload!\n\n \"\"\"\n Apply a coupon code to the active cart. Appends to the existing set and returns\n the recomputed cart, or typed errors when the code is rejected.\n \"\"\"\n cartCouponApply(\n \"\"\"Coupon code to apply.\"\"\"\n input: CartCouponApplyInput!\n ): CartCouponApplyPayload!\n\n \"\"\"\n Remove a coupon code from the active cart. Idempotent when the code is not applied.\n \"\"\"\n cartCouponRemove(\n \"\"\"Coupon code to remove.\"\"\"\n input: CartCouponRemoveInput!\n ): CartCouponRemovePayload!\n\n \"\"\"\n Re-check the active cart against the platform and return it with each line's\n availability refreshed. Writes nothing: the cart is re-simulated and only\n `items.variant.availability` is corrected, so a line the platform can no longer\n fulfil comes back flagged for the shopper to remove. Quantities are never\n altered, and totals stay as the cart reported them until that removal\n recomputes them. Takes no arguments: the cart is resolved from the request\n context and is never created.\n \"\"\"\n cartValidate: CartValidatePayload!\n}\n\nextend type Mutation {\n \"\"\"\n Write the provided profile fields to the active cart's clientProfileData\n attachment. Partial: omitted fields are untouched. Never mints a cart.\n \"\"\"\n clientProfileUpdate(\n \"\"\"Profile fields to write.\"\"\"\n input: ClientProfileUpdateInput!\n ): ClientProfileUpdatePayload!\n\n \"\"\"\n Write the provided preference flags to the active cart's clientPreferencesData\n attachment. Partial: omitted flags are untouched. Never mints a cart.\n \"\"\"\n clientPreferencesUpdate(\n \"\"\"Preference flags to write.\"\"\"\n input: ClientPreferencesUpdateInput!\n ): ClientPreferencesUpdatePayload!\n\n \"\"\"\n Set the full delivery address (delivery step). Auto-selects the cheapest SLA\n when available. Never mints a cart.\n \"\"\"\n shippingAddressUpdate(\n \"\"\"Address details to write.\"\"\"\n input: ShippingAddressUpdateInput!\n ): ShippingAddressUpdatePayload!\n\n \"\"\"\n Select one shipping option for the whole cart. Fans the selection out to\n every item. Never mints a cart.\n \"\"\"\n shippingOptionSelect(\n \"\"\"The option to select.\"\"\"\n input: ShippingOptionSelectInput!\n ): ShippingOptionSelectPayload!\n}\n\nextend type Mutation {\n \"\"\"\n Select a payment method, optionally identifying a new card's BIN or a saved card's account.\n \"\"\"\n paymentMethodSelect(\n \"\"\"Method (and optional BIN/saved-card) to select.\"\"\"\n input: PaymentMethodSelectInput!\n ): PaymentMethodSelectPayload!\n\n \"\"\"\n Select an installment count for the currently-selected method. Recomputes the cart total.\n \"\"\"\n paymentInstallmentSelect(\n \"\"\"Installment count to select.\"\"\"\n input: PaymentInstallmentSelectInput!\n ): PaymentInstallmentSelectPayload!\n\n \"\"\"Set the billing address, or fall back to the delivery address.\"\"\"\n paymentBillingAddressUpdate(\n \"\"\"\n Billing address details, or sameAsDelivery: true to use the delivery address.\n \"\"\"\n input: PaymentBillingAddressUpdateInput!\n ): PaymentBillingAddressUpdatePayload!\n\n \"\"\"\n Open a VTEX transaction from the currently-selected payment. No input: every field is server-derived.\n \"\"\"\n paymentStartTransaction: PaymentStartTransactionPayload!\n\n \"\"\"\n Finalize a transaction after the direct-to-gateway payment call succeeded.\n \"\"\"\n paymentGatewayCallback(\n \"\"\"The order group to finalize.\"\"\"\n input: PaymentGatewayCallbackInput!\n ): PaymentGatewayCallbackPayload!\n}\n\nextend type Mutation {\n \"\"\"\n Sign in with email and password. Returns the refreshed session, or typed\n userErrors.\n \"\"\"\n sessionLoginWithPassword(\n \"\"\"The email and password to authenticate with.\"\"\"\n input: SessionPasswordLoginInput!\n ): SessionLoginPayload!\n\n \"\"\"\n Request an email access code. Returns an opaque authenticationToken to\n round-trip into validate.\n \"\"\"\n sessionRequestAccessCode(\n \"\"\"The email to send the access code to.\"\"\"\n input: SessionRequestAccessCodeInput!\n ): SessionRequestAccessCodePayload!\n\n \"\"\"\n Validate an email access code with its authenticationToken. Returns the\n refreshed session.\n \"\"\"\n sessionValidateAccessCode(\n \"\"\"The email, access code, and authenticationToken to validate.\"\"\"\n input: SessionValidateAccessCodeInput!\n ): SessionLoginPayload!\n\n \"\"\"\n Sign out. Clears auth cookies and refreshes the order form to a guest session.\n Idempotent.\n \"\"\"\n sessionLogout: SessionLoginPayload!\n}\n\nextend type Mutation {\n \"\"\"\n Set the shopper's active region from a postal code. Accepts the value as the\n shopper typed it, including any mask; the server normalizes it and derives the\n city.\n \"\"\"\n regionUpdate(\n \"\"\"The postal code to activate, masked or bare.\"\"\"\n postalCode: String!\n ): RegionUpdatePayload!\n}\n\nextend type Mutation {\n \"\"\"\n Partially update the signed-in shopper's own profile. Upserts: succeeds whether or not a record exists.\n \"\"\"\n customerProfileUpdate(\n \"\"\"The fields to write. Provide only what the shopper edited.\"\"\"\n input: CustomerProfileUpdateInput!\n ): CustomerProfileUpdatePayload!\n}\n\n\"\"\"\nA manufacturer or designer brand.\nMaps to VTEX Brand entity.\n\"\"\"\ntype Brand implements Node\n @cacheControl(maxAge: 300, scope: PUBLIC)\n{\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Human-readable brand name.\"\"\"\n name: String!\n\n \"\"\"URL-friendly slug.\"\"\"\n slug: String!\n\n \"\"\"Whether the brand is currently active in the storefront.\"\"\"\n isActive: Boolean!\n\n \"\"\"Brand logo image.\"\"\"\n logo: Image\n\n \"\"\"SEO metadata.\"\"\"\n seo: SEO\n\n \"\"\"Paginated list of products belonging to this brand.\"\"\"\n products(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: ProductSortingInput\n\n \"\"\"Filter criteria.\"\"\"\n filter: ProductsFilterInput\n ): ProductsConnection!\n}\n\n\"\"\"Paginated list of brands.\"\"\"\ntype BrandsConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [BrandEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Total number of brands matching the query.\"\"\"\n totalCount: Int!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype BrandEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Brand!\n}\n\n\"\"\"\nA hierarchical product classification level (department → category → subcategory).\nMaps to VTEX Category entity. The tree structure is modeled via parent/children\nreferences, allowing arbitrary depth.\n\"\"\"\ntype Category implements Node\n @cacheControl(maxAge: 300, scope: PUBLIC)\n{\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Display name of the category.\"\"\"\n name: String!\n\n \"\"\"URL-friendly slug.\"\"\"\n slug: String!\n\n \"\"\"Full materialized path, e.g. \\\"/clothing/shirts/casual\\\".\"\"\"\n path: String!\n\n \"\"\"Zero-indexed depth in the tree (0 = department).\"\"\"\n level: Int!\n\n \"\"\"Whether this category is currently active.\"\"\"\n isActive: Boolean!\n\n \"\"\"Parent category. Null for top-level departments.\"\"\"\n parent: Category\n\n \"\"\"Direct children of this category.\"\"\"\n children: [Category!]!\n\n \"\"\"SEO metadata.\"\"\"\n seo: SEO\n\n \"\"\"Paginated products within this category.\"\"\"\n products(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: ProductSortingInput\n\n \"\"\"Filter criteria.\"\"\"\n filter: ProductsFilterInput\n ): ProductsConnection!\n\n \"\"\"Specification groups relevant to this category.\"\"\"\n specificationGroups: [SpecificationGroup!]!\n}\n\n\"\"\"Paginated list of categories.\"\"\"\ntype CategoriesConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [CategoryEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Total number of categories matching the query.\"\"\"\n totalCount: Int!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype CategoryEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Category!\n}\n\n\"\"\"\nA group of related specifications that apply to products in a category.\nMaps to VTEX Specification Group.\n\"\"\"\ntype SpecificationGroup {\n \"\"\"Display name (e.g. \\\"Physical Attributes\\\", \\\"Technical Details\\\").\"\"\"\n name: String!\n\n \"\"\"Specifications in this group.\"\"\"\n specifications: [Specification!]!\n}\n\n\"\"\"\nA product-level or variant-level specification field.\nMaps to VTEX Specification / Specification Field.\nExamples: \"Material\", \"Warranty Period\" (product-level);\n\"Color\", \"Size\" (variant-level / option axis).\n\"\"\"\ntype Specification {\n \"\"\"Display label (e.g. \\\"Material\\\").\"\"\"\n name: String!\n\n \"\"\"The assigned values for this specification.\"\"\"\n values: [String!]!\n\n \"\"\"How this spec was captured.\"\"\"\n fieldType: SpecificationFieldType!\n}\n\n\"\"\"A single breadcrumb entry in the product's navigation trail.\"\"\"\ntype BreadcrumbItem {\n \"\"\"Human-readable label (e.g. \"Electronics\", \"Smartphones\").\"\"\"\n name: String!\n\n \"\"\"URL path for this breadcrumb level (e.g. \"/electronics/smartphones\").\"\"\"\n url: String!\n\n \"\"\"1-based position in the breadcrumb trail.\"\"\"\n position: Int!\n}\n\n\"\"\"\nDiscriminates how a specification value was captured.\nMaps to VTEX FieldType numeric codes.\n\"\"\"\nenum SpecificationFieldType {\n \"\"\"Free text.\"\"\"\n TEXT\n\n \"\"\"Multi-line free text.\"\"\"\n MULTI_LINE_TEXT\n\n \"\"\"Single-select dropdown.\"\"\"\n COMBO\n\n \"\"\"Single-select radio.\"\"\"\n RADIO\n\n \"\"\"Multi-select checkbox.\"\"\"\n CHECKBOX\n\n \"\"\"Rich text / HTML.\"\"\"\n RICH_TEXT\n}\n\n\"\"\"\nA named option axis on a product that differentiates variants.\nThink \"Color\" or \"Size\". Each option has a set of possible values.\nSimilar to Shopify's ProductOption.\n\"\"\"\ntype ProductOption {\n \"\"\"Option name (e.g. \\\"Color\\\").\"\"\"\n name: String!\n\n \"\"\"\n All values available across variants (e.g. [\\\"Red\\\", \\\"Blue\\\", \\\"Green\\\"]).\n \"\"\"\n values: [String!]!\n}\n\n\"\"\"\nA concrete option/value pair selected on a specific variant.\nExample: { name: \"Color\", value: \"Red\" }.\n\"\"\"\ntype SelectedOption {\n \"\"\"Option axis name.\"\"\"\n name: String!\n\n \"\"\"Selected value.\"\"\"\n value: String!\n}\n\n\"\"\"\nA purchasable unit of a product.\nMaps to VTEX SKU. In commerce, this is the concrete item a customer adds to cart —\na specific size/color/voltage combination of a product.\n\"\"\"\ntype Variant implements Node {\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Variant display name (e.g. \\\"Varsity Red — Size 10\\\").\"\"\"\n name: String!\n\n \"\"\"Stock Keeping Unit code. Unique merchant-side identifier.\"\"\"\n sku: String!\n\n \"\"\"\n PDP path segment in the active locale for this SKU, \"{linkId}-{skuId}\".\n \"\"\"\n routeSlug: String!\n\n \"\"\"International Article Number (barcode). Maps to VTEX SKU EAN.\"\"\"\n ean: String\n\n \"\"\"Merchant reference code.\"\"\"\n referenceId: String\n\n \"\"\"Whether this variant is currently available for purchase.\"\"\"\n availableForSale: Boolean!\n\n \"\"\"Current selling price.\"\"\"\n price: Money!\n\n \"\"\"Original list price before promotions. Null when there is no discount.\"\"\"\n compareAtPrice: Money\n\n \"\"\"All option selections that define this variant.\"\"\"\n selectedOptions: [SelectedOption!]!\n\n \"\"\"Variant images. First image is the primary.\"\"\"\n images(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int = 10\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): ImagesConnection!\n\n \"\"\"Variant videos.\"\"\"\n videos: [Video!]!\n\n \"\"\"Quantity currently in stock. Null when inventory tracking is disabled.\"\"\"\n quantityAvailable: Int\n\n \"\"\"Unit of sale (e.g. \\\"un\\\", \\\"kg\\\", \\\"l\\\").\"\"\"\n measurementUnit: String!\n\n \"\"\"\n Multiplier for the measurement unit (e.g. 1.5 means each unit is 1.5 kg).\n \"\"\"\n unitMultiplier: Float!\n\n \"\"\"The parent product.\"\"\"\n product: Product!\n\n \"\"\"Variant-level specifications (e.g. voltage, plug type).\"\"\"\n specifications: [Specification!]!\n\n \"\"\"Add-on customizations available for this variant.\"\"\"\n customizations: [Customization!]!\n\n \"\"\"Sellers that fulfill this variant. Relevant in marketplace scenarios.\"\"\"\n sellers: [Seller!]!\n}\n\n\"\"\"Paginated list of variants.\"\"\"\ntype VariantsConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [VariantEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Total number of variants matching the query.\"\"\"\n totalCount: Int!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype VariantEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Variant!\n}\n\n\"\"\"\nA product is a generic item in the catalog (e.g. \"Nike Zoom Janoski\").\nEach product has one or more variants (SKUs) that are the purchasable units.\nMaps to the VTEX Product entity.\n\"\"\"\ntype Product implements Node\n @key(fields: \"slug\")\n @cacheControl(maxAge: 60, scope: PUBLIC)\n{\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Product title as displayed in the storefront.\"\"\"\n title: String!\n\n \"\"\"URL-friendly handle / slug. Maps to VTEX `LinkId`.\"\"\"\n slug: String!\n\n \"\"\"\n PDP path segment in the active locale, \"{linkId}-{skuId}\" with the representative SKU (in stock first, then lowest\n spot price, then the first SKU). Build product URLs from this, not from `slug`.\n \"\"\"\n routeSlug: String!\n\n \"\"\"Full marketing description.\"\"\"\n description: String\n\n \"\"\"Description rendered as HTML.\"\"\"\n descriptionHtml: HTML\n\n \"\"\"Vendor / manufacturer name. Shortcut for brand.name.\"\"\"\n vendor: String\n\n \"\"\"Whether this product is currently active on the storefront.\"\"\"\n isActive: Boolean!\n\n \"\"\"Whether to show the product even when out of stock.\"\"\"\n showWhenOutOfStock: Boolean!\n\n \"\"\"Date the product was first published.\"\"\"\n publishedAt: DateTime\n\n \"\"\"Release or launch date.\"\"\"\n releaseDate: DateTime\n\n \"\"\"Date the product was created in the system.\"\"\"\n createdAt: DateTime!\n\n \"\"\"Date the product was last updated.\"\"\"\n updatedAt: DateTime!\n\n \"\"\"Merchant reference ID. Maps to VTEX `RefId`.\"\"\"\n referenceId: String\n\n \"\"\"The brand this product belongs to.\"\"\"\n brand: Brand\n\n \"\"\"The primary (most specific) category.\"\"\"\n category: Category\n\n \"\"\"All ancestor categories up to the root department.\"\"\"\n categories: [Category!]!\n\n \"\"\"The price range across all variants.\"\"\"\n priceRange: PriceRange!\n\n \"\"\"The compare-at (original) price range across all variants.\"\"\"\n compareAtPriceRange: PriceRange\n\n \"\"\"Named option axes available on this product (e.g. Color, Size).\"\"\"\n options: [ProductOption!]!\n\n \"\"\"Product-level specifications (e.g. material, care instructions).\"\"\"\n specifications: [Specification!]!\n\n \"\"\"Specification groups with full detail.\"\"\"\n specificationGroups: [SpecificationGroup!]!\n\n \"\"\"All purchasable variants.\"\"\"\n variants(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): VariantsConnection!\n\n \"\"\"Total number of variants.\"\"\"\n totalVariants: Int!\n\n \"\"\"Product images aggregated from all variants.\"\"\"\n images(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int = 10\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): ImagesConnection!\n\n \"\"\"All media (images + videos) for this product.\"\"\"\n media(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int = 12\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): MediaConnection!\n\n \"\"\"SEO metadata.\"\"\"\n seo: SEO\n\n \"\"\"Navigation breadcrumb trail from root category to this product.\"\"\"\n breadcrumbs: [BreadcrumbItem!]!\n\n \"\"\"User-generated review / rating summary.\"\"\"\n rating: Rating\n\n \"\"\"\n Products related to this one (accessories, suggestions). Maps to VTEX SKU Complement.\n \"\"\"\n relatedProducts(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int = 10\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): ProductsConnection!\n\n \"\"\"Collections this product belongs to.\"\"\"\n collections: [Collection!]!\n\n \"\"\"\n Sales channels where this product is available. Maps to VTEX Trade Policy.\n \"\"\"\n salesChannels: [SalesChannel!]!\n\n \"\"\"Bundle components if this product is a kit. Maps to VTEX SKU Kit.\"\"\"\n bundleItems: [BundleItem!]!\n}\n\n\"\"\"Paginated list of products.\"\"\"\ntype ProductsConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [ProductEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Total number of products matching the query.\"\"\"\n totalCount: Int!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype ProductEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Product!\n}\n\n\"\"\"Paginated list of images.\"\"\"\ntype ImagesConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [ImageEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype ImageEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Image!\n}\n\n\"\"\"Paginated list of media.\"\"\"\ntype MediaConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [MediaEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype MediaEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Media!\n}\n\n\"\"\"\nAggregate review/rating summary for a product.\nMaps to VTEX Product Review Rate.\n\"\"\"\ntype Rating {\n \"\"\"Average score (e.g. 4.3).\"\"\"\n average: Float!\n\n \"\"\"Total number of reviews.\"\"\"\n count: Int!\n}\n\n\"\"\"\nA curated group of products. Maps to VTEX Collection (Beta) entity.\nCollections can be used for seasonal promotions, editorial picks, etc.\n\"\"\"\ntype Collection implements Node {\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Collection title.\"\"\"\n title: String!\n\n \"\"\"URL-friendly slug.\"\"\"\n slug: String!\n\n \"\"\"Editorial description.\"\"\"\n description: String\n\n \"\"\"Whether the collection is currently active.\"\"\"\n isActive: Boolean!\n\n \"\"\"Hero / banner image.\"\"\"\n image: Image\n\n \"\"\"SEO metadata.\"\"\"\n seo: SEO\n\n \"\"\"Paginated products in this collection.\"\"\"\n products(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: ProductSortingInput\n\n \"\"\"Filter criteria.\"\"\"\n filter: ProductsFilterInput\n ): ProductsConnection!\n}\n\n\"\"\"Paginated list of collections.\"\"\"\ntype CollectionsConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [CollectionEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Total number of collections matching the query.\"\"\"\n totalCount: Int!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype CollectionEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Collection!\n}\n\n\"\"\"\nA seller / merchant that fulfills orders for one or more variants.\nMaps to VTEX Seller entity.\n\"\"\"\ntype Seller implements Node {\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Seller display name.\"\"\"\n name: String!\n\n \"\"\"Whether this is the default (1P) seller.\"\"\"\n isDefault: Boolean!\n\n \"\"\"Seller logo.\"\"\"\n logo: Image\n\n \"\"\"\n The seller's commercial offer for this variant (price, availability, installments).\n \"\"\"\n offer: Offer\n}\n\n\"\"\"\nA seller's commercial offer for a specific variant — includes pricing,\navailability, and available payment/installment options.\n\"\"\"\ntype Offer {\n \"\"\"Current selling price.\"\"\"\n price: Money!\n\n \"\"\"Original list price before promotions.\"\"\"\n listPrice: Money!\n\n \"\"\"\n Best cash/immediate-payment price (e.g. Pix discount). Equals price when there is no spot discount.\n \"\"\"\n spotPrice: Money!\n\n \"\"\"Price before any cart-level or coupon discounts.\"\"\"\n priceWithoutDiscount: Money!\n\n \"\"\"Available quantity from this seller.\"\"\"\n availableQuantity: Int!\n\n \"\"\"Tax amount.\"\"\"\n tax: Money!\n\n \"\"\"\n Tax as a percentage of the price (e.g. 0.18 for 18%). Null when not applicable.\n \"\"\"\n taxPercentage: Float\n\n \"\"\"Loyalty / reward points value earned on this purchase.\"\"\"\n rewardValue: Float!\n\n \"\"\"\n ISO 8601 timestamp after which this price is no longer guaranteed. Null when open-ended.\n \"\"\"\n priceValidUntil: DateTime\n\n \"\"\"SKU IDs of free gifts included with this offer.\"\"\"\n giftSkuIds: [String!]!\n\n \"\"\"\n Named discount labels applied to this offer (e.g. \\\"10% off\\\", \\\"Black Friday\\\").\n \"\"\"\n discountHighlights: [String!]!\n\n \"\"\"Available installment plans grouped by payment method.\"\"\"\n installmentPlans: [InstallmentPlan!]!\n\n \"\"\"\n Active promotions applicable to this offer (e.g. bundle discounts, buy-X-get-Y).\n \"\"\"\n promotions: [Promotion!]!\n}\n\n\"\"\"\nA group of installment options for a single payment method (e.g. \"Visa\", \"Pix\").\n\"\"\"\ntype InstallmentPlan {\n \"\"\"Payment method name (e.g. \\\"Visa\\\", \\\"Mastercard\\\", \\\"Pix\\\").\"\"\"\n paymentMethod: String!\n\n \"\"\"Payment method category.\"\"\"\n paymentType: PaymentType!\n\n \"\"\"\n Available installment tiers, sorted by number of installments ascending.\n \"\"\"\n installments: [Installment!]!\n}\n\n\"\"\"\nA single installment tier within a payment method.\nExample: 6 × R$ 61.65 at 0% interest.\n\"\"\"\ntype Installment {\n \"\"\"Number of installments (1 = single payment).\"\"\"\n count: Int!\n\n \"\"\"Amount per installment.\"\"\"\n value: Money!\n\n \"\"\"Total amount including any interest.\"\"\"\n totalAmount: Money!\n\n \"\"\"Annual interest rate as a percentage (0 = interest-free).\"\"\"\n interestRate: Float!\n\n \"\"\"Whether this tier is interest-free.\"\"\"\n interestFree: Boolean!\n}\n\n\"\"\"Broad category of a payment method.\"\"\"\nenum PaymentType {\n \"\"\"Credit card (Visa, Mastercard, Amex, etc.).\"\"\"\n CREDIT_CARD\n\n \"\"\"Debit card.\"\"\"\n DEBIT_CARD\n\n \"\"\"Instant / real-time payment (Pix, etc.).\"\"\"\n INSTANT\n\n \"\"\"Bank slip / boleto.\"\"\"\n BANK_SLIP\n\n \"\"\"Digital wallet or store-specific payment.\"\"\"\n DIGITAL_WALLET\n\n \"\"\"Other / unknown payment type.\"\"\"\n OTHER\n}\n\n\"\"\"\nAn active promotion or incentive applicable to an offer.\nExamples: \"Buy 3 for R$14.99\", \"9% Pix discount\".\n\"\"\"\ntype Promotion {\n \"\"\"Human-readable promotion name / headline.\"\"\"\n name: String!\n\n \"\"\"Conditions that must be met for the promotion to apply.\"\"\"\n conditions: PromotionConditions!\n\n \"\"\"Effects applied when conditions are met.\"\"\"\n effects: PromotionEffects!\n}\n\n\"\"\"Conditions required for a promotion to activate.\"\"\"\ntype PromotionConditions {\n \"\"\"Minimum quantity of items required (0 = no minimum).\"\"\"\n minimumQuantity: Int!\n\n \"\"\"\n Additional condition parameters (e.g. required payment method, collection membership).\n \"\"\"\n parameters: [PromotionParameter!]!\n}\n\n\"\"\"Effects granted when a promotion's conditions are satisfied.\"\"\"\ntype PromotionEffects {\n \"\"\"Effect parameters describing the discount or benefit.\"\"\"\n parameters: [PromotionParameter!]!\n}\n\n\"\"\"\nA key-value parameter describing a promotion condition or effect.\nCommon keys: \\\"PercentualDiscount\\\", \\\"MaximumUnitPriceDiscount\\\",\n\\\"PaymentMethodId\\\", \\\"CollectionsIdsList\\\".\n\"\"\"\ntype PromotionParameter {\n \"\"\"Parameter key.\"\"\"\n name: String!\n\n \"\"\"Parameter value.\"\"\"\n value: String!\n}\n\n\"\"\"\nA trade policy / sales channel through which products are sold.\nMaps to VTEX Trade Policy / Sales Channel.\n\"\"\"\ntype SalesChannel implements Node\n @cacheControl(maxAge: 3600, scope: PUBLIC)\n{\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Channel name.\"\"\"\n name: String!\n\n \"\"\"ISO 4217 default currency for this channel.\"\"\"\n currencyCode: CurrencyCode!\n\n \"\"\"BCP 47 locale for this channel.\"\"\"\n locale: Locale\n\n \"\"\"Whether the channel is active.\"\"\"\n isActive: Boolean!\n}\n\n\"\"\"\nAn optional, free-form customization that can be attached to a variant.\nMaps to VTEX Attachment. Examples: gift wrapping, engraving, custom message.\n\"\"\"\ntype Customization implements Node {\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Display name (e.g. \\\"Gift Message\\\").\"\"\"\n name: String!\n\n \"\"\"Whether this customization is required.\"\"\"\n isRequired: Boolean!\n\n \"\"\"Allowed keys and their constraints.\"\"\"\n fields: [CustomizationField!]!\n}\n\n\"\"\"A single input field within a customization.\"\"\"\ntype CustomizationField {\n \"\"\"Field key (e.g. \\\"message\\\").\"\"\"\n key: String!\n\n \"\"\"Maximum allowed characters.\"\"\"\n maxLength: Int\n\n \"\"\"Regex or enum-based validation domain.\"\"\"\n allowedValues: [String!]\n}\n\n\"\"\"\nA component of a product bundle / kit.\nMaps to VTEX SKU Kit.\n\"\"\"\ntype BundleItem {\n \"\"\"The variant included in the bundle.\"\"\"\n variant: Variant!\n\n \"\"\"Quantity of this variant in the bundle.\"\"\"\n quantity: Int!\n}\n\n\"\"\"Filter criteria for product queries.\"\"\"\ninput ProductsFilterInput {\n \"\"\"Full-text search term.\"\"\"\n searchTerm: String\n\n \"\"\"Include only products in these category IDs.\"\"\"\n categoryIds: [ID!]\n\n \"\"\"Include only products from these brand IDs.\"\"\"\n brandIds: [ID!]\n\n \"\"\"Minimum price filter.\"\"\"\n priceMin: Decimal\n\n \"\"\"Maximum price filter.\"\"\"\n priceMax: Decimal\n\n \"\"\"Include only available (in-stock) products.\"\"\"\n availableOnly: Boolean\n\n \"\"\"Filter by product type / taxonomy label.\"\"\"\n productType: String\n\n \"\"\"\n Filter by specification values, e.g. {name: \\\"Color\\\", values: [\\\"Red\\\", \\\"Blue\\\"]}.\n \"\"\"\n specifications: [SpecificationFilterInput!]\n\n \"\"\"Filter by sales channel ID.\"\"\"\n salesChannelId: ID\n\n \"\"\"Filter by collection ID.\"\"\"\n collectionId: ID\n\n \"\"\"Include only products published after this date.\"\"\"\n publishedAfter: DateTime\n\n \"\"\"Include only products published before this date.\"\"\"\n publishedBefore: DateTime\n\n \"\"\"Filter by tags / keywords.\"\"\"\n tags: [String!]\n}\n\n\"\"\"Allows filtering products by specification name and values.\"\"\"\ninput SpecificationFilterInput {\n \"\"\"Specification name (e.g. \\\"Color\\\").\"\"\"\n name: String!\n\n \"\"\"Accepted values (OR semantics within, AND across different specs).\"\"\"\n values: [String!]!\n}\n\n\"\"\"Sorting input for product queries.\"\"\"\ninput ProductSortingInput {\n \"\"\"Field to sort on.\"\"\"\n field: ProductSortField!\n\n \"\"\"Sort direction.\"\"\"\n direction: SortDirection!\n}\n\n\"\"\"Fields available for sorting products.\"\"\"\nenum ProductSortField {\n \"\"\"Alphabetical by title.\"\"\"\n TITLE\n\n \"\"\"By price (uses min variant price).\"\"\"\n PRICE\n\n \"\"\"By creation date.\"\"\"\n CREATED_AT\n\n \"\"\"By last update date.\"\"\"\n UPDATED_AT\n\n \"\"\"By release date.\"\"\"\n RELEASE_DATE\n\n \"\"\"By relevance / scoring.\"\"\"\n RELEVANCE\n\n \"\"\"By number of sales.\"\"\"\n BEST_SELLING\n}\n\n\"\"\"Filter criteria for collection queries.\"\"\"\ninput CollectionsFilterInput {\n \"\"\"Full-text search term.\"\"\"\n searchTerm: String\n\n \"\"\"Include only active collections.\"\"\"\n activeOnly: Boolean\n}\n\n\"\"\"Sorting input for collection queries.\"\"\"\ninput CollectionSortingInput {\n \"\"\"Field to sort on.\"\"\"\n field: CollectionSortField!\n\n \"\"\"Sort direction.\"\"\"\n direction: SortDirection!\n}\n\n\"\"\"Fields available for sorting collections.\"\"\"\nenum CollectionSortField {\n \"\"\"Alphabetical by title.\"\"\"\n TITLE\n\n \"\"\"By creation date.\"\"\"\n CREATED_AT\n\n \"\"\"By last update date.\"\"\"\n UPDATED_AT\n}\n\n\"\"\"Sorting input for brand queries.\"\"\"\ninput BrandSortingInput {\n \"\"\"Field to sort on.\"\"\"\n field: BrandSortField!\n\n \"\"\"Sort direction.\"\"\"\n direction: SortDirection!\n}\n\n\"\"\"Fields available for sorting brands.\"\"\"\nenum BrandSortField {\n \"\"\"Alphabetical by name.\"\"\"\n NAME\n\n \"\"\"By creation date.\"\"\"\n CREATED_AT\n}\n\n\"\"\"Sorting input for category queries.\"\"\"\ninput CategorySortingInput {\n \"\"\"Field to sort on.\"\"\"\n field: CategorySortField!\n\n \"\"\"Sort direction.\"\"\"\n direction: SortDirection!\n}\n\n\"\"\"Fields available for sorting categories.\"\"\"\nenum CategorySortField {\n \"\"\"Alphabetical by name.\"\"\"\n NAME\n\n \"\"\"By depth level in the tree.\"\"\"\n LEVEL\n}\n\n\"\"\"\nThe result of a full-text product search, including facets for building\nfilter UI.\n\"\"\"\ntype ProductSearchResult {\n \"\"\"Paginated product results.\"\"\"\n products(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): ProductsConnection!\n\n \"\"\"Available facets/filters derived from the result set.\"\"\"\n facets: [SearchFacet!]!\n\n \"\"\"Corrected or suggested query if the original had a typo.\"\"\"\n suggestedQuery: String\n\n \"\"\"Spelling correction details for the query.\"\"\"\n correction: SearchCorrection\n}\n\n\"\"\"Spelling correction information returned by the search engine.\"\"\"\ntype SearchCorrection {\n \"\"\"Whether the original query was corrected.\"\"\"\n corrected: Boolean!\n\n \"\"\"Whether the original query was misspelled.\"\"\"\n misspelled: Boolean!\n\n \"\"\"The corrected query text.\"\"\"\n text: String\n\n \"\"\"The corrected text with `<em>` tags highlighting the changed parts.\"\"\"\n highlighted: String\n}\n\n\"\"\"A facet returned from search, representing a filterable dimension.\"\"\"\ntype SearchFacet {\n \"\"\"Facet name (e.g. \\\"Brand\\\", \\\"Color\\\", \\\"Price Range\\\").\"\"\"\n name: String!\n\n \"\"\"\n Facet key used in filter parameters (e.g. \\\"brand\\\", \\\"category-1\\\", \\\"price\\\").\n \"\"\"\n key: String!\n\n \"\"\"Type discriminator for rendering the correct UI control.\"\"\"\n type: SearchFacetType!\n\n \"\"\"Whether this facet should be hidden from the UI.\"\"\"\n hidden: Boolean!\n\n \"\"\"Total number of values in this facet.\"\"\"\n quantity: Int!\n\n \"\"\"Individual values within this facet.\"\"\"\n values: [SearchFacetValue!]!\n}\n\n\"\"\"Discriminates the facet UI control type.\"\"\"\nenum SearchFacetType {\n \"\"\"A list of discrete values (brand, color, size).\"\"\"\n LIST\n\n \"\"\"A numeric range (price).\"\"\"\n RANGE\n}\n\n\"\"\"A single value within a search facet.\"\"\"\ntype SearchFacetValue {\n \"\"\"Display label.\"\"\"\n label: String!\n\n \"\"\"Value to send back in the filter.\"\"\"\n value: String!\n\n \"\"\"Number of products matching this facet value.\"\"\"\n count: Int!\n\n \"\"\"Whether the user has currently selected this value.\"\"\"\n isSelected: Boolean!\n\n \"\"\"For RANGE facets: the lower bound.\"\"\"\n rangeFrom: Float\n\n \"\"\"For RANGE facets: the upper bound.\"\"\"\n rangeTo: Float\n\n \"\"\"Navigable href for this facet selection.\"\"\"\n href: String\n}\n\n\"\"\"Recommendation strategy hint.\"\"\"\nenum RecommendationIntent {\n \"\"\"Products related by purchase history.\"\"\"\n RELATED\n\n \"\"\"Products frequently bought together.\"\"\"\n COMPLEMENTARY\n\n \"\"\"Similar products (same category / specs).\"\"\"\n SIMILAR\n}\n\n\"\"\"An autocomplete suggestion returned for a partial search query.\"\"\"\ntype AutocompleteSuggestion {\n \"\"\"The suggested search term.\"\"\"\n term: String!\n\n \"\"\"Estimated number of results for this term.\"\"\"\n count: Int!\n\n \"\"\"\n Suggested facet filters associated with this term (e.g. department, category).\n \"\"\"\n attributes: [AutocompleteAttribute!]!\n}\n\n\"\"\"A facet attribute suggested alongside an autocomplete term.\"\"\"\ntype AutocompleteAttribute {\n \"\"\"Attribute key (e.g. \\\"category-1\\\").\"\"\"\n key: String!\n\n \"\"\"Attribute value (e.g. \\\"alimentos-e-bebidas\\\").\"\"\"\n value: String!\n\n \"\"\"Human-readable label for the key (e.g. \\\"Departamento\\\").\"\"\"\n labelKey: String!\n\n \"\"\"Human-readable label for the value (e.g. \\\"Alimentos e bebidas\\\").\"\"\"\n labelValue: String!\n}\n\n\"\"\"A trending search term.\"\"\"\ntype TrendingSearch {\n \"\"\"The search term.\"\"\"\n term: String!\n\n \"\"\"Number of searches in the recent period.\"\"\"\n count: Int!\n}\n\n\"\"\"Identifies a catalog route. Set exactly one member.\"\"\"\ninput CatalogRouteInput {\n \"\"\"\n Relay node ID of the entity behind the route. Only Product IDs are accepted.\n \"\"\"\n id: ID\n\n \"\"\"\n Logical store route, without the binding's origin or base path\n (e.g. \"/apparel/t-shirts\", \"/shirt-51/p\").\n \"\"\"\n path: String\n}\n\n\"\"\"Kind of entity a catalog route resolves to.\"\"\"\nenum CatalogRouteKind {\n \"\"\"A product detail page.\"\"\"\n PRODUCT\n\n \"\"\"A category or subcategory page.\"\"\"\n CATEGORY\n\n \"\"\"A brand page.\"\"\"\n BRAND\n\n \"\"\"A collection page.\"\"\"\n COLLECTION\n}\n\n\"\"\"\nA catalog route and its confirmed equivalents in every locale the catalog registers.\n\"\"\"\ntype CatalogRoute\n @cacheControl(maxAge: 300, scope: PUBLIC)\n{\n \"\"\"\n Relay node ID of the entity. Stable across locales; the route's identity.\n \"\"\"\n id: ID!\n\n \"\"\"Kind of entity behind the route.\"\"\"\n kind: CatalogRouteKind!\n\n \"\"\"\n One entry per locale the catalog confirms for every segment of the route. Editorial fallback never adds an\n entry. May include locales the store does not publish; intersect with the store's bindings.\n \"\"\"\n variants: [CatalogRouteVariant!]!\n}\n\n\"\"\"A route's equivalent in one locale.\"\"\"\ntype CatalogRouteVariant {\n \"\"\"BCP 47 tag, canonicalized.\"\"\"\n locale: String!\n\n \"\"\"\n Route segment(s) in that locale, lowercased, accents kept. Not percent-encoded.\n \"\"\"\n slug: String!\n\n \"\"\"\n Relative logical path (\"/{slug}\" or \"/{slug}/p\"). No origin, no base path, not percent-encoded.\n \"\"\"\n path: String!\n}\n\n\"\"\"\nA shopping cart representing items a customer intends to purchase.\nMaps to the VTEX orderForm entity.\n\"\"\"\ntype Cart implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Globally unique identifier (Relay Node ID).\"\"\"\n id: ID!\n\n \"\"\"Line items currently in the cart.\"\"\"\n items: [CartItem!]!\n\n \"\"\"Aggregated totals (items, shipping, discounts, etc.).\"\"\"\n totalizers: [CartTotalizer!]!\n\n \"\"\"Total cart value.\"\"\"\n total: Money!\n\n \"\"\"\n Server messages from cart processing (e.g. promotions applied, stock warnings).\n \"\"\"\n messages: [CartMessage!]!\n\n \"\"\"\n The coupon codes currently applied to the cart. Empty when none are applied.\n \"\"\"\n appliedCoupons: [AppliedCoupon!]!\n\n \"\"\"\n Cart-level discounts as labeled lines: one per promotion or coupon, plus a\n residual for whatever they do not account for. Always a list (possibly empty),\n never null.\n \n The itemized companion to the aggregated \"Discounts\" totalizer, which stays on\n `totalizers` unchanged: these entries sum to it, so a summary built on them\n cannot under-report. Distinct from the per-line `CartItem.discounts`, which is\n featured-only and itemized per price tag rather than per promotion.\n \"\"\"\n discounts: [CartDiscount!]!\n\n \"\"\"\n Aggregate item-level catalog markdown: the sum of `(listPrice - price) * quantity`\n across non-gift lines, clamped at zero (in cents from VTEX, exposed as Money). This\n is the reduction from each line's list price down to the price the \"Items\" subtotal\n is built on — the catalog \"de/por\" savings baked into the price before any\n cart-level promotion runs.\n \n Distinct money from the cart-level promotions and coupons on the \"Discounts\"\n totalizer and `discounts` (those are `price - sellingPrice`, the reduction below the\n subtotal): item markdown sits *above* the subtotal, so it is never part of\n `totalizers` and never summed into `total`. It is an informational savings figure and\n is zero when no line is marked down. Gift lines are excluded — a gift's value is\n already a promotion entry in `discounts`.\n \"\"\"\n itemMarkdownTotal: Money!\n\n \"\"\"\n Progress toward the cart's nearest free-shipping promotion. Null when no\n eligible free-shipping promotion is identified for this cart.\n \"\"\"\n freeShippingProgress: FreeShippingProgress\n\n \"\"\"\n Non-null when the cart belongs to a different sales channel than the request's active context. The\n cart is left exactly as it was: nothing here reprices it or removes an item. Converging it is a\n write, and the first write that carries the active channel makes Checkout reprocess the same\n orderForm — prices, promotions, coupons and logistics are recomputed and items not sellable in the\n destination channel are removed by Checkout, while orderFormId, attachments and addresses survive.\n \"\"\"\n salesChannelMismatch: CartSalesChannelMismatch\n}\n\n\"\"\"\nThe two sales channels that disagree. Both are trade-policy identifiers.\n\"\"\"\ntype CartSalesChannelMismatch {\n \"\"\"The channel the cart is currently on.\"\"\"\n cartSalesChannel: String!\n\n \"\"\"The channel the request's active context resolves to.\"\"\"\n activeSalesChannel: String!\n}\n\n\"\"\"\nProgress toward the cart's nearest free-shipping promotion, derived from the\npromotion teasers on a checkout simulation run with the cart's destination.\nNull until the cart has a destination, and when no eligible free-shipping\npromotion is identified for this cart (none configured, none teaser-emitting,\nempty cart, or unreadable promotion data) — absence is a normal state, never\nan error.\n\"\"\"\ntype FreeShippingProgress {\n \"\"\"\n Minimum qualifying cart total for the promotion. Null when the cart already\n qualifies: the promotion teaser disappears once met, so the threshold is no\n longer reported upstream.\n \"\"\"\n threshold: Money\n\n \"\"\"Amount still to add to qualify. Zero when the cart qualifies.\"\"\"\n remaining: Money!\n\n \"\"\"Whether the cart currently qualifies for free shipping.\"\"\"\n qualified: Boolean!\n}\n\n\"\"\"\nOne labeled cart-level discount line, attributed to the promotion or coupon that\ngranted it. Amounts are summed from the cart's price tags and reconciled against\nthe aggregated \"Discounts\" totalizer.\n\"\"\"\ntype CartDiscount {\n \"\"\"\n The promotion's name, or the coupon code exactly as the shopper entered it.\n Null when nothing upstream names this amount: always for OTHER, and for a\n promotion the platform left unnamed. The consumer supplies the label in that\n case; an internal promotion identifier is never exposed here.\n \"\"\"\n name: String\n\n \"\"\"\n Amount taken off by this line, as a positive monetary value. The minus sign is\n presentational and applied by the UI. Never zero: a promotion that took nothing\n off this cart is omitted rather than reported as an empty line.\n \"\"\"\n value: Money!\n\n \"\"\"\n Where this line came from: an automatic promotion, a coupon, or the residual.\n \"\"\"\n kind: CartDiscountKind!\n}\n\n\"\"\"Origin of a cart-level discount line.\"\"\"\nenum CartDiscountKind {\n \"\"\"An automatically applied promotion.\"\"\"\n PROMOTION\n\n \"\"\"A promotion matched by a coupon code the shopper entered.\"\"\"\n COUPON\n\n \"\"\"\n The part of the aggregate discount that no named promotion or coupon accounts\n for — a promotion the merchant did not feature, a discount the platform never\n itemized, or rounding. At most one entry, always last.\n \"\"\"\n OTHER\n}\n\n\"\"\"\nA coupon code applied to the cart, with whether its promotion conditions matched.\n\"\"\"\ntype AppliedCoupon {\n \"\"\"The coupon code, stored verbatim as the shopper entered it.\"\"\"\n code: String!\n\n \"\"\"\n True when the code is on the cart but its promotion conditions did not match\n (no discount granted).\n \"\"\"\n isUnmatchedCondition: Boolean!\n}\n\n\"\"\"\nA single line item in the cart. Carries a historical snapshot of the variant\nand product at the time it was added: cart-line value objects describe what\nthe customer actually has, not what the catalog says about it today.\n\"\"\"\ntype CartItem {\n \"\"\"\n Cart-scoped line identifier (opaque, stable across mutations for the same line).\n Encoded as Base64(\"CartItem:<orderFormId>:<uniqueId>\"). Not a Relay Node; this id\n exists only to serve as a stable cache key for clients doing optimistic UI.\n \"\"\"\n id: ID!\n\n \"\"\"\n Variant snapshot for this line. Built from the order line itself, not from\n a live catalog read.\n \"\"\"\n variant: CartVariant!\n\n \"\"\"\n Product snapshot for this line. Built from the order line itself, not from\n a live catalog read.\n \"\"\"\n product: CartProduct!\n\n \"\"\"Quantity of this variant in the cart.\"\"\"\n quantity: Int!\n\n \"\"\"Unit price (in cents from VTEX, exposed as Money).\"\"\"\n price: Money!\n\n \"\"\"Original list price before discounts.\"\"\"\n listPrice: Money!\n\n \"\"\"Final selling price after discounts.\"\"\"\n sellingPrice: Money!\n\n \"\"\"Whether this line is a gift item.\"\"\"\n gift: Boolean!\n\n \"\"\"\n Discounts applied to this line, one entry per featured promotion price tag that\n carries a currency amount. Always a list (possibly empty), never null. The\n per-line, itemized companion to the aggregated \"Discounts\" totalizer on Cart —\n not a replacement for it.\n \n Percentage-configured promotions are absent: their price tag carries a rate\n rather than an amount, so there is no monetary value to report per line. Use\n `Cart.discounts` for a breakdown that accounts for every discount, naming what\n it can and reporting the rest as a residual.\n \"\"\"\n discounts: [CartItemDiscount!]!\n\n \"\"\"\n Upstream line identifier (order-form uniqueId). The stable per-line address:\n with customizations, the same variant can appear as several distinct lines,\n so variantId is not unique per line. Stable across cart mutations.\n \"\"\"\n lineId: String!\n\n \"\"\"\n This line's own total (own unit selling price × quantity, rounding-exact),\n excluding composition children.\n \"\"\"\n ownTotal: Money!\n\n \"\"\"\n This line's total including all nested composition children, recursively.\n Equals ownTotal when the line has no compositions.\n \"\"\"\n total: Money!\n\n \"\"\"\n Form-based customizations applied to this line (personalization text,\n selections, uploaded-file URLs). Empty when the line has none.\n \"\"\"\n customizations: [CartItemCustomization!]!\n\n \"\"\"\n Child products added to this line by bundle/assembly choices. Children are\n not top-level cart lines; they ship, and are removed, with their parent.\n \"\"\"\n compositions: [CartItemComposition!]!\n\n \"\"\"\n The cart-wide delivery options this item is eligible for: the same ShippingOption\n entities Shipping.options lists, carrying the shared-pool price, not a per-item\n quote. Excludes pickup options; delivery channel only.\n \"\"\"\n shippingOptions: [ShippingOption!]!\n\n \"\"\"\n This item's own currently selected delivery option, or null when no selection\n is persisted or the persisted selection is a pickup option.\n \"\"\"\n selectedShippingOption: ItemShippingOption\n}\n\n\"\"\"\nOne customization group applied to a cart line, keyed by the upstream\nassembly id. Entries are the shopper's submitted key/value pairs.\n\"\"\"\ntype CartItemCustomization {\n \"\"\"Upstream assembly identifier (e.g. \"Engraving\", \"Optional features\").\"\"\"\n id: String!\n\n \"\"\"\n Submitted values, one entry per input field. File uploads carry the file URL\n as the value.\n \"\"\"\n entries: [CartItemCustomizationEntry!]!\n}\n\n\"\"\"A single submitted customization field.\"\"\"\ntype CartItemCustomizationEntry\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Field name as defined by the assembly (e.g. \"Text\").\"\"\"\n name: String!\n\n \"\"\"Submitted value, stringified. File uploads carry the file URL.\"\"\"\n value: String!\n}\n\n\"\"\"\nA child product nested under a cart line by a bundle/assembly choice. Slim by\ndesign: children are not independently addressable cart lines. May carry its\nown children (nested assemblies).\n\"\"\"\ntype CartItemComposition {\n \"\"\"\n Cart-scoped identifier for this child line (same opaque encoding as\n CartItem.id). Cache key only; not independently mutable.\n \"\"\"\n id: ID!\n\n \"\"\"Display name as recorded on the order line.\"\"\"\n name: String!\n\n \"\"\"Quantity of this child in the composition.\"\"\"\n quantity: Int!\n\n \"\"\"Unit selling price of the child.\"\"\"\n price: Money!\n\n \"\"\"\n The child's own availability. The parent's variant.availability already\n reflects the worst state across the whole composition.\n \"\"\"\n availability: CartItemAvailability!\n\n \"\"\"\n The assembly group that added this child (upstream parentAssemblyBinding,\n e.g. \"Material Product_Material\").\n \"\"\"\n assemblyId: String!\n\n \"\"\"This child's own total, excluding its nested children.\"\"\"\n ownTotal: Money!\n\n \"\"\"This child's total including its nested children, recursively.\"\"\"\n total: Money!\n\n \"\"\"\n Form-based customizations applied to this child. A composition child is\n itself an order-form line and can carry its own input assemblies. Empty\n when it has none.\n \"\"\"\n customizations: [CartItemCustomization!]!\n\n \"\"\"Nested composition children of this child. Empty when it has none.\"\"\"\n compositions: [CartItemComposition!]!\n}\n\n\"\"\"\nA single discount applied to a cart line, broken out from that line's price tags.\nSourced from items[].priceTags filtered to featured promotions carrying a currency\namount; the aggregate discount total stays on the Cart \"Discounts\" totalizer.\n\"\"\"\ntype CartItemDiscount {\n \"\"\"\n Human-readable label resolved from the matched promotion (e.g. \"Black Friday\").\n Falls back to the price tag identifier when the promotion has no name.\n \"\"\"\n name: String!\n\n \"\"\"\n Amount taken off by this discount, as a positive monetary value. The minus\n sign is presentational and applied by the UI.\n \"\"\"\n value: Money!\n\n \"\"\"\n Always false. A price tag expressing a percentage carries a rate rather than a\n currency amount, so it has no value this type can report and is excluded from\n the list. Retained for compatibility; branching on it gains nothing.\n \"\"\"\n isPercentual: Boolean!\n}\n\n\"\"\"\nStock availability status for a cart line. Mapped from the upstream\norder-form availability strings; UNKNOWN absorbs values we haven't classified.\n\"\"\"\nenum CartItemAvailability {\n \"\"\"In stock and shippable.\"\"\"\n AVAILABLE\n\n \"\"\"Known to be out of stock.\"\"\"\n OUT_OF_STOCK\n\n \"\"\"\n Other known-bad states (cannot deliver, fulfillment unavailable, etc.).\n \"\"\"\n UNAVAILABLE\n\n \"\"\"Upstream returned a state we don't recognize yet.\"\"\"\n UNKNOWN\n}\n\n\"\"\"\nCart-line snapshot of a Variant. Value object: holds the line-level facts\nrecorded at add-to-cart time. Does not implement Node; refetch the live\ncatalog Variant via Query.node(variantId) when needed.\n\"\"\"\ntype CartVariant {\n \"\"\"\n Relay Node ID of the catalog Variant this line refers to. Use it with\n Query.node to refetch the live Variant.\n \"\"\"\n variantId: ID!\n\n \"\"\"Stock-keeping unit code as recorded on the order line.\"\"\"\n sku: String!\n\n \"\"\"\n Display name as recorded on the order line (e.g. \"Classic T-Shirt - Red / M\").\n \"\"\"\n name: String!\n\n \"\"\"\n Single image captured at add-to-cart time. Null when no image URL was recorded.\n \"\"\"\n image: Image\n\n \"\"\"\n International Article Number (barcode). May be empty for items without one.\n \"\"\"\n ean: String\n\n \"\"\"Merchant SKU reference code.\"\"\"\n referenceId: String\n\n \"\"\"Availability classification of this line.\"\"\"\n availability: CartItemAvailability!\n\n \"\"\"Unit of sale (e.g. \"un\", \"kg\").\"\"\"\n measurementUnit: String!\n\n \"\"\"Multiplier for the measurement unit.\"\"\"\n unitMultiplier: Float!\n\n \"\"\"Seller fulfilling this line.\"\"\"\n seller: CartSellerRef!\n\n \"\"\"\n Variations selected on this cart line (e.g. Cor: Vermelho, Tamanho: M). [] when the SKU has\n no variations, never null. Resolved server-side from catalog data (ADR-00012); use this field\n instead of a Query.node(variantId) refetch to read a line's selected options.\n \"\"\"\n selectedOptions: [SelectedOption!]!\n}\n\n\"\"\"\nCart-line snapshot of a Product. Value object: holds the line-level facts\nrecorded at add-to-cart time. Does not implement Node; refetch the live\ncatalog Product via Query.node(productId) when needed.\n\"\"\"\ntype CartProduct {\n \"\"\"\n Relay Node ID of the catalog Product. Use it with Query.node to refetch\n the live Product.\n \"\"\"\n productId: ID!\n\n \"\"\"Product title as recorded on the order line.\"\"\"\n title: String!\n\n \"\"\"Product detail URL as recorded on the order line.\"\"\"\n detailUrl: String!\n\n \"\"\"Categories root-to-leaf as recorded on the order line.\"\"\"\n categories: [CartCategoryRef!]!\n\n \"\"\"\n Brand reference as recorded on the order line. Null when the product has no brand.\n \"\"\"\n brand: CartBrandRef\n}\n\n\"\"\"\nLightweight reference to a Category captured on a cart line. Carries enough\nidentity to deep-link back to the live Category via Query.node.\n\"\"\"\ntype CartCategoryRef {\n \"\"\"Relay Node ID of the catalog Category.\"\"\"\n categoryId: ID!\n\n \"\"\"Category name as recorded on the order line.\"\"\"\n name: String!\n}\n\n\"\"\"Lightweight reference to a Brand captured on a cart line.\"\"\"\ntype CartBrandRef {\n \"\"\"Relay Node ID of the catalog Brand.\"\"\"\n brandId: ID!\n\n \"\"\"Brand name as recorded on the order line.\"\"\"\n name: String!\n}\n\n\"\"\"Lightweight reference to a Seller captured on a cart line.\"\"\"\ntype CartSellerRef {\n \"\"\"Relay Node ID of the catalog Seller.\"\"\"\n sellerId: ID!\n\n \"\"\"Seller display name as recorded on the order line.\"\"\"\n name: String!\n}\n\n\"\"\"\nAn aggregated total section in the cart (e.g. Items, Shipping, Discounts).\n\"\"\"\ntype CartTotalizer {\n \"\"\"Totalizer key (e.g. \"Items\", \"Shipping\", \"Discounts\").\"\"\"\n id: String!\n\n \"\"\"Human-readable label.\"\"\"\n name: String!\n\n \"\"\"Totalizer value.\"\"\"\n value: Money!\n}\n\n\"\"\"\nA server message attached to the cart (promotions applied, warnings, etc.).\n\"\"\"\ntype CartMessage {\n \"\"\"Message code identifier.\"\"\"\n code: String\n\n \"\"\"Human-readable message text.\"\"\"\n text: String!\n\n \"\"\"Message severity (e.g. \"info\", \"warning\", \"error\").\"\"\"\n status: String!\n}\n\n\"\"\"Input for adding an item to a cart.\"\"\"\ninput CartItemAddInput {\n \"\"\"The variant's global Node ID to add.\"\"\"\n variantId: ID!\n\n \"\"\"Quantity to add. Defaults to 1.\"\"\"\n quantity: Int = 1\n\n \"\"\"\n The seller's global Node ID. Defaults to the first-party seller (Seller:1).\n \"\"\"\n seller: ID = \"U2VsbGVyOjE=\"\n}\n\n\"\"\"Input for removing an item from a cart.\"\"\"\ninput CartItemRemoveInput {\n \"\"\"The variant's global Node ID to remove.\"\"\"\n variantId: ID!\n}\n\n\"\"\"Input for removing multiple items from a cart in one call.\"\"\"\ninput CartItemsRemoveInput {\n \"\"\"The variants' global Node IDs to remove. Duplicates are collapsed.\"\"\"\n variantIds: [ID!]!\n}\n\n\"\"\"Input for increasing the quantity of a cart item.\"\"\"\ninput CartItemQuantityIncreaseInput {\n \"\"\"The variant's global Node ID whose quantity to increase.\"\"\"\n variantId: ID!\n\n \"\"\"Amount to add to the current quantity. Defaults to 1.\"\"\"\n quantityToAdd: Int = 1\n}\n\n\"\"\"Input for decreasing the quantity of a cart item.\"\"\"\ninput CartItemQuantityDecreaseInput {\n \"\"\"The variant's global Node ID whose quantity to decrease.\"\"\"\n variantId: ID!\n\n \"\"\"Amount to subtract from the current quantity. Defaults to 1.\"\"\"\n quantityToRemove: Int = 1\n}\n\n\"\"\"Input for applying a coupon code to the cart.\"\"\"\ninput CartCouponApplyInput {\n \"\"\"The coupon code to apply, sent verbatim.\"\"\"\n code: String!\n}\n\n\"\"\"Input for removing a coupon code from the cart.\"\"\"\ninput CartCouponRemoveInput {\n \"\"\"The coupon code to remove from the applied set.\"\"\"\n code: String!\n}\n\n\"\"\"Error codes for cart item add operations.\"\"\"\nenum CartItemAddErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The quantity must be a positive integer.\"\"\"\n INVALID_QUANTITY\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The provided variant ID is not a valid Variant Node ID.\"\"\"\n INVALID_VARIANT_ID\n\n \"\"\"The provided seller ID is not a valid Seller Node ID.\"\"\"\n INVALID_SELLER_ID\n}\n\n\"\"\"Error codes for cart item remove operations.\"\"\"\nenum CartItemRemoveErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The specified variant was not found in the cart.\"\"\"\n ITEM_NOT_FOUND\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The provided variant ID is not a valid Variant Node ID.\"\"\"\n INVALID_VARIANT_ID\n}\n\n\"\"\"Error codes for cart items batch remove operations.\"\"\"\nenum CartItemsRemoveErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"None of the specified variants were found in the cart.\"\"\"\n ITEM_NOT_FOUND\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"A provided variant ID is not a valid Variant Node ID.\"\"\"\n INVALID_VARIANT_ID\n\n \"\"\"The input contained no variant IDs.\"\"\"\n NO_VARIANT_IDS_PROVIDED\n\n \"\"\"The upstream checkout API returned an unusable response.\"\"\"\n UPSTREAM_ERROR\n}\n\n\"\"\"Error codes for cart item quantity increase operations.\"\"\"\nenum CartItemQuantityIncreaseErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The quantity must be a positive integer.\"\"\"\n INVALID_QUANTITY\n\n \"\"\"The specified variant was not found in the cart.\"\"\"\n ITEM_NOT_FOUND\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The provided variant ID is not a valid Variant Node ID.\"\"\"\n INVALID_VARIANT_ID\n}\n\n\"\"\"Error codes for cart item quantity decrease operations.\"\"\"\nenum CartItemQuantityDecreaseErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The quantity must be a positive integer.\"\"\"\n INVALID_QUANTITY\n\n \"\"\"The specified variant was not found in the cart.\"\"\"\n ITEM_NOT_FOUND\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The provided variant ID is not a valid Variant Node ID.\"\"\"\n INVALID_VARIANT_ID\n}\n\n\"\"\"A domain error returned from a cart item add mutation.\"\"\"\ntype CartItemAddError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartItemAddErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a cart item remove mutation.\"\"\"\ntype CartItemRemoveError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartItemRemoveErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a cart items batch remove mutation.\"\"\"\ntype CartItemsRemoveError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartItemsRemoveErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a cart item quantity increase mutation.\"\"\"\ntype CartItemQuantityIncreaseError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartItemQuantityIncreaseErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a cart item quantity decrease mutation.\"\"\"\ntype CartItemQuantityDecreaseError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartItemQuantityDecreaseErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the cartItemAdd mutation.\"\"\"\ntype CartItemAddPayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartItemAddError!]!\n}\n\n\"\"\"Payload returned by the cartItemRemove mutation.\"\"\"\ntype CartItemRemovePayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartItemRemoveError!]!\n}\n\n\"\"\"Payload returned by the cartItemsRemove mutation.\"\"\"\ntype CartItemsRemovePayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartItemsRemoveError!]!\n}\n\n\"\"\"Payload returned by the cartItemQuantityIncrease mutation.\"\"\"\ntype CartItemQuantityIncreasePayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartItemQuantityIncreaseError!]!\n}\n\n\"\"\"Payload returned by the cartItemQuantityDecrease mutation.\"\"\"\ntype CartItemQuantityDecreasePayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartItemQuantityDecreaseError!]!\n}\n\n\"\"\"Error codes for cart coupon apply operations.\"\"\"\nenum CartCouponApplyErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The coupon code is empty, unknown, or not accepted by this cart.\"\"\"\n COUPON_NOT_FOUND\n\n \"\"\"The coupon code has expired.\"\"\"\n COUPON_EXPIRED\n}\n\n\"\"\"Error codes for cart coupon remove operations.\"\"\"\nenum CartCouponRemoveErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n A surviving coupon was rejected by the cart after the removal (unknown or not accepted).\n \"\"\"\n COUPON_NOT_FOUND\n\n \"\"\"A surviving coupon has expired.\"\"\"\n COUPON_EXPIRED\n}\n\n\"\"\"A domain error returned from a cart coupon apply mutation.\"\"\"\ntype CartCouponApplyError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartCouponApplyErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a cart coupon remove mutation.\"\"\"\ntype CartCouponRemoveError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartCouponRemoveErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the cartCouponApply mutation.\"\"\"\ntype CartCouponApplyPayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartCouponApplyError!]!\n}\n\n\"\"\"Payload returned by the cartCouponRemove mutation.\"\"\"\ntype CartCouponRemovePayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartCouponRemoveError!]!\n}\n\n\"\"\"Error codes for cart validation operations.\"\"\"\nenum CartValidateErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n The upstream checkout API could not be reached or returned an unusable response.\n \"\"\"\n UPSTREAM_ERROR\n}\n\n\"\"\"A domain error returned from a cart validate mutation.\"\"\"\ntype CartValidateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartValidateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the cartValidate mutation.\"\"\"\ntype CartValidatePayload {\n \"\"\"The re-checked cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartValidateError!]!\n}\n\n\"\"\"The delivery channel for a shipping option.\"\"\"\nenum DeliveryChannel {\n \"\"\"Home delivery.\"\"\"\n DELIVERY\n\n \"\"\"Pickup in store or pickup point.\"\"\"\n PICKUP\n\n \"\"\"Unrecognised delivery channel returned by the fulfillment provider.\"\"\"\n UNKNOWN\n}\n\n\"\"\"The unit of a shipping time estimate.\"\"\"\nenum ShippingEstimateUnit {\n \"\"\"Calendar days (e.g. \"7d\").\"\"\"\n DAYS\n\n \"\"\"Business days, excluding weekends and holidays (e.g. \"5bd\").\"\"\"\n BUSINESS_DAYS\n\n \"\"\"Hours (e.g. \"7h\").\"\"\"\n HOURS\n\n \"\"\"Minutes (e.g. \"119m\").\"\"\"\n MINUTES\n}\n\n\"\"\"\nParsed shipping time estimate (e.g. \"5bd\" → { value: 5, unit: BUSINESS_DAYS }).\n\"\"\"\ntype ShippingEstimate {\n \"\"\"Numeric quantity of the estimate.\"\"\"\n value: Int!\n\n \"\"\"Time unit for the estimate.\"\"\"\n unit: ShippingEstimateUnit!\n}\n\n\"\"\"\nOne cart item's current selection. Its price and estimate are Checkout's real values for\nthis item; option carries the shared name and channel. All values are a live snapshot:\nanother item joining or leaving the same SLA can change them.\n\"\"\"\ntype ItemShippingOption {\n \"\"\"\n Opaque delivery option identifier, encoded to distinguish per-item options\n from cart-wide options so the Apollo cache never conflates them.\n \"\"\"\n id: ID!\n\n \"\"\"The cart-wide shipping option this selection belongs to.\"\"\"\n option: ShippingOption!\n\n \"\"\"Estimated shipping time for this item.\"\"\"\n shippingEstimate: ShippingEstimate!\n\n \"\"\"Shipping cost for this item. Zero when free shipping applies.\"\"\"\n price: Money!\n\n \"\"\"Shipping list price for this item before any promotions.\"\"\"\n listPrice: Money!\n}\n\n\"\"\"\nA shipping option with at least one cart item currently selected on it. Its price and\nshippingEstimateList cover only those items, recomputed on every request.\n\"\"\"\ntype SelectedShippingOption {\n \"\"\"The cart-wide shipping option these items selected.\"\"\"\n option: ShippingOption!\n\n \"\"\"\n Cart items currently selected on this option (not every eligible item).\n \"\"\"\n items: [CartItem!]!\n\n \"\"\"Sum of items[].selectedShippingOption.price.\"\"\"\n price: Money!\n\n \"\"\"Sum of items[].selectedShippingOption.listPrice.\"\"\"\n listPrice: Money!\n\n \"\"\"\n items[].selectedShippingOption.shippingEstimate, one entry per item, unreduced and\n uncompared.\n \"\"\"\n shippingEstimateList: [ShippingEstimate!]!\n}\n\n\"\"\"A shipping option available for a given item and destination.\"\"\"\ntype ShippingOption {\n \"\"\"Opaque delivery option identifier.\"\"\"\n id: ID!\n\n \"\"\"\n Human-readable option name (e.g. \"Standard Delivery\"). Derived from a Checkout simulation, which\n sends no `Accept-Language` and keys its SKU cache without a locale, so this text is NOT localized:\n it may read in the account's default culture on a non-default binding.\n \"\"\"\n name: String!\n\n \"\"\"Delivery channel: home delivery or pickup.\"\"\"\n channel: DeliveryChannel!\n\n \"\"\"\n Estimated shipping time, sampled from one eligible item. Delivery estimates can genuinely vary\n per item under the same option; use shippingEstimateList for every item's own value.\n \"\"\"\n shippingEstimate: ShippingEstimate! @deprecated(reason: \"Sampled from one item and can misrepresent the others. Use shippingEstimateList.\") @deprecationDate(deletionDate: \"2027-03-31\")\n\n \"\"\"\n Every eligible item's own shippingEstimate, one entry per item, unreduced and uncompared.\n \"\"\"\n shippingEstimateList: [ShippingEstimate!]!\n\n \"\"\"\n Shipping cost for every item this option covers. Zero when free shipping applies. A live\n estimate of the shared per-SLA pool, not a guaranteed quote.\n \"\"\"\n price: Money!\n\n \"\"\"Shipping list price before any promotions, summed like price.\"\"\"\n listPrice: Money!\n\n \"\"\"Cart items for which this option is currently available.\"\"\"\n items: [CartItem!]!\n}\n\n\"\"\"Error codes for shipping simulation operations.\"\"\"\nenum ShippingSimulateErrorCode {\n \"\"\"The postal code was not found or rejected by the fulfillment provider.\"\"\"\n INVALID_POSTAL_CODE\n\n \"\"\"The provided variant ID is not a valid Variant Node ID.\"\"\"\n INVALID_VARIANT_ID\n\n \"\"\"The provided seller ID is not a valid Seller Node ID.\"\"\"\n INVALID_SELLER_ID\n\n \"\"\"\n No active cart in context: the cartShippingSimulation query requires an x-order-form-id and never mints a cart.\n \"\"\"\n MISSING_CART_CONTEXT\n}\n\n\"\"\"A domain error returned from the shippingSimulation query.\"\"\"\ntype ShippingSimulateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: ShippingSimulateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the shippingSimulation query.\"\"\"\ntype ShippingSimulationPayload {\n \"\"\"\n Available shipping options for the given item and destination.\n Null when userErrors is non-empty.\n \"\"\"\n shippingOptions: [ShippingOption!]\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ShippingSimulateError!]!\n}\n\n\"\"\"Input for simulating shipping options for a single variant on a PDP.\"\"\"\ninput ShippingSimulateInput {\n \"\"\"Postal code of the delivery destination.\"\"\"\n postalCode: String!\n\n \"\"\"ISO 3166-1 alpha-3 country code (e.g. \"BRA\").\"\"\"\n country: String!\n\n \"\"\"The variant's global Node ID to simulate shipping for.\"\"\"\n variantId: ID!\n\n \"\"\"Quantity of the variant. Must be at least 1.\"\"\"\n quantity: Int!\n\n \"\"\"The seller's global Node ID responsible for the variant.\"\"\"\n seller: ID!\n}\n\n\"\"\"\nInput for simulating shipping for the whole active cart. Carries the\ndestination only; items are sourced from the cart in the x-order-form-id\ncontext, so there is no variant, quantity, or seller.\n\"\"\"\ninput CartShippingSimulateInput {\n \"\"\"Postal code of the delivery destination.\"\"\"\n postalCode: String!\n\n \"\"\"ISO 3166-1 alpha-3 country code (e.g. \"BRA\").\"\"\"\n country: String!\n}\n\n\"\"\"Payload returned by the cartShippingSimulation query.\"\"\"\ntype CartShippingSimulationPayload {\n \"\"\"\n Shipping options available for every item in the cart, prices summed across\n items. Empty when the cart is empty or no option is common to all items.\n Null when userErrors is non-empty.\n \"\"\"\n shippingOptions: [ShippingOption!]\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ShippingSimulateError!]!\n}\n\n\"\"\"\nThe shopper's profile, projected from the order form's clientProfileData.\nA sibling of Cart, not nested under it. Individual (B2C) only.\n\"\"\"\ntype ClientProfile implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order form ID.\n \"\"\"\n id: ID!\n\n \"\"\"\n Contact email as recorded on the order form. Null when no profile is set.\n \"\"\"\n email: String\n\n \"\"\"Given name. Null when unset.\"\"\"\n firstName: String\n\n \"\"\"Family name. Null when unset.\"\"\"\n lastName: String\n\n \"\"\"Contact phone in the order form's stored format. Null when unset.\"\"\"\n phone: String\n\n \"\"\"Tax document (CPF for Individual shoppers). Null when unset.\"\"\"\n document: String\n}\n\n\"\"\"\nThe shopper's preference opt-ins, projected from the order form's\nclientPreferencesData. A sibling of Cart, not nested under it.\n\"\"\"\ntype ClientPreferences implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order form ID.\n \"\"\"\n id: ID!\n\n \"\"\"Newsletter opt-in. Null when the shopper has not chosen.\"\"\"\n optinNewsLetter: Boolean\n\n \"\"\"Save-personal-data opt-in. Null when the shopper has not chosen.\"\"\"\n savePersonalData: Boolean\n}\n\n\"\"\"\nPartial profile update. Provide only the fields to write; omitted fields are\nleft unchanged.\n\"\"\"\ninput ClientProfileUpdateInput {\n \"\"\"Contact email to write. Omit to leave unchanged.\"\"\"\n email: String\n\n \"\"\"Given name to write. Omit to leave unchanged.\"\"\"\n firstName: String\n\n \"\"\"Family name to write. Omit to leave unchanged.\"\"\"\n lastName: String\n\n \"\"\"Contact phone to write. Omit to leave unchanged.\"\"\"\n phone: String\n\n \"\"\"Tax document to write. Omit to leave unchanged.\"\"\"\n document: String\n}\n\n\"\"\"\nPartial preferences update. Provide only the flags to write; omitted flags are\nleft unchanged.\n\"\"\"\ninput ClientPreferencesUpdateInput {\n \"\"\"Newsletter opt-in to write. Omit to leave unchanged.\"\"\"\n optinNewsLetter: Boolean\n\n \"\"\"Save-personal-data opt-in to write. Omit to leave unchanged.\"\"\"\n savePersonalData: Boolean\n}\n\n\"\"\"Error codes for client profile update operations.\"\"\"\nenum ClientProfileUpdateErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The input contained no profile fields to write.\"\"\"\n NO_PROFILE_FIELD_PROVIDED\n}\n\n\"\"\"Error codes for client preferences update operations.\"\"\"\nenum ClientPreferencesUpdateErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The input contained no preferences to write.\"\"\"\n NO_PREFERENCE_PROVIDED\n}\n\n\"\"\"A domain error returned from a client profile update mutation.\"\"\"\ntype ClientProfileUpdateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: ClientProfileUpdateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a client preferences update mutation.\"\"\"\ntype ClientPreferencesUpdateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: ClientPreferencesUpdateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the clientProfileUpdate mutation.\"\"\"\ntype ClientProfileUpdatePayload {\n \"\"\"The updated profile, or null if a context/validation error occurred.\"\"\"\n clientProfile: ClientProfile\n\n \"\"\"\n The session after the write, reflecting the now-current isIdentified derived from\n clientProfileData.email. Null alongside clientProfile on a context/validation error.\n \"\"\"\n session: Session\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ClientProfileUpdateError!]!\n}\n\n\"\"\"Payload returned by the clientPreferencesUpdate mutation.\"\"\"\ntype ClientPreferencesUpdatePayload {\n \"\"\"\n The updated preferences, or null if a context/validation error occurred.\n \"\"\"\n clientPreferences: ClientPreferences\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ClientPreferencesUpdateError!]!\n}\n\n\"\"\"\nThe delivery address, saved addresses, and shipping options for the cart.\nA read-only sibling of Cart, projected from the same order form fetch.\nNull fields project explicitly (never undefined), allowing partial queries.\n\"\"\"\ntype Shipping implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order form ID.\n \"\"\"\n id: ID!\n\n \"\"\"The delivery address on the cart. Null until the shopper provides one.\"\"\"\n address: Address\n\n \"\"\"The logged-in shopper's saved addresses. Empty for anonymous shoppers.\"\"\"\n savedAddresses: [Address!]!\n\n \"\"\"\n Delivery-channel shipping options any cart item is eligible for at the current address, prices summed across the eligible items.\n \"\"\"\n options: [ShippingOption!]!\n\n \"\"\"\n Delivery-channel options with at least one item currently selected on them, one entry per option. Pickup selections are excluded.\n \"\"\"\n selectedOptions: [SelectedShippingOption!]!\n\n \"\"\"\n The selected option, when one selection applies to the whole cart. Null when items disagree or the persisted selection is a pickup-channel SLA.\n \"\"\"\n selectedOptionId: ID @deprecated(reason: \"Cannot express split shipping. Use Shipping.selectedOptions.\") @deprecationDate(deletionDate: \"2027-03-31\")\n}\n\n\"\"\"A delivery address or saved-address reference.\"\"\"\ntype Address\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Saved-address reference. Null for addresses entered in this session.\"\"\"\n id: ID\n\n \"\"\"Recipient name for this address.\"\"\"\n recipientName: String\n\n \"\"\"\n Postal code. Required when persisting an address via the mutation inputs, but nullable here:\n pickup/search/disposable addresses and legacy saved addresses can come back without one.\n \"\"\"\n postalCode: String\n\n \"\"\"\n ISO 3166-1 alpha-3 country code. Required when persisting an address via the mutation inputs,\n but nullable here for the same reason as postalCode.\n \"\"\"\n country: String\n\n \"\"\"Street name.\"\"\"\n street: String\n\n \"\"\"Street number.\"\"\"\n number: String\n\n \"\"\"Additional address detail (apartment, suite, etc.).\"\"\"\n complement: String\n\n \"\"\"Neighborhood / district.\"\"\"\n neighborhood: String\n\n \"\"\"City name.\"\"\"\n city: String\n\n \"\"\"State / province code.\"\"\"\n state: String\n}\n\n\"\"\"\nThe shopper's session and authentication state for the active order form.\nA read-only sibling of Cart, projected from the same order form fetch. Carries\nno profile data — profile fields live on ClientProfile.\n\"\"\"\ntype Session implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order form ID.\n \"\"\"\n id: ID!\n\n \"\"\"\n Whether the shopper holds an authenticated VTEX session. A guest is `false` — a normal state, never an error.\n \"\"\"\n loggedIn: Boolean!\n\n \"\"\"\n Whether the shopper may edit their identity data. Lets Controls render editable vs. identified-but-masked fields.\n \"\"\"\n canEditData: Boolean!\n\n \"\"\"\n Server-computed: whether a profile is attached to the order form (clientProfileData.email is present and non-empty). Identification is distinct from authentication.\n \"\"\"\n isIdentified: Boolean!\n}\n\n\"\"\"Input for updating the delivery address on the cart.\"\"\"\ninput ShippingAddressUpdateInput {\n \"\"\"\n Saved address reference. Present when the shopper picked (and possibly edited) a saved address.\n \"\"\"\n savedAddressId: ID\n\n \"\"\"Recipient name for the delivery address.\"\"\"\n recipientName: String!\n\n \"\"\"Postal code for the delivery address.\"\"\"\n postalCode: String!\n\n \"\"\"ISO 3166-1 alpha-3 country code (e.g. \"BRA\").\"\"\"\n country: String!\n\n \"\"\"Street name.\"\"\"\n street: String\n\n \"\"\"Street number.\"\"\"\n number: String\n\n \"\"\"Additional address detail (apartment, suite, etc.).\"\"\"\n complement: String\n\n \"\"\"Neighborhood / district.\"\"\"\n neighborhood: String\n\n \"\"\"City name.\"\"\"\n city: String\n\n \"\"\"State / province code.\"\"\"\n state: String\n}\n\n\"\"\"Error codes for shipping address update operations.\"\"\"\nenum ShippingAddressUpdateErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The recipient name is blank.\"\"\"\n MISSING_RECIPIENT_NAME\n\n \"\"\"The provided saved address ID does not match any available addresses.\"\"\"\n INVALID_SAVED_ADDRESS_ID\n\n \"\"\"The postal code is invalid or not found.\"\"\"\n INVALID_POSTAL_CODE\n\n \"\"\"The address is invalid.\"\"\"\n INVALID_ADDRESS\n}\n\n\"\"\"A domain error returned from a shipping address update mutation.\"\"\"\ntype ShippingAddressUpdateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: ShippingAddressUpdateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the shippingAddressUpdate mutation.\"\"\"\ntype ShippingAddressUpdatePayload {\n \"\"\"The updated shipping state, or null if errors occurred.\"\"\"\n shipping: Shipping\n\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ShippingAddressUpdateError!]!\n}\n\n\"\"\"Input for selecting a shipping option for the whole cart.\"\"\"\ninput ShippingOptionSelectInput {\n \"\"\"\n The shipping option ID to select. Must be offered as a delivery option by at least one item in\n the cart; items that don't offer it fall back to the Checkout API's own choice.\n \"\"\"\n optionId: ID!\n}\n\n\"\"\"Error codes for shipping option select operations.\"\"\"\nenum ShippingOptionSelectErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n The option id is not offered as a delivery option by any item in the cart (unknown id or\n pickup). Returned without calling Checkout. An option only some items offer is not an error.\n \"\"\"\n OPTION_UNAVAILABLE\n\n \"\"\"No delivery address is persisted on the cart.\"\"\"\n MISSING_SHIPPING_ADDRESS\n}\n\n\"\"\"A domain error returned from a shipping option select mutation.\"\"\"\ntype ShippingOptionSelectError {\n \"\"\"Machine-readable error code.\"\"\"\n code: ShippingOptionSelectErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the shippingOptionSelect mutation.\"\"\"\ntype ShippingOptionSelectPayload {\n \"\"\"The updated shipping state, or null if errors occurred.\"\"\"\n shipping: Shipping\n\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ShippingOptionSelectError!]!\n}\n\n\"\"\"\nThe cart's payment state: available methods with server-driven validator config,\nthe current selection, installment options, and the shopper's saved cards. A\nread-only sibling of Cart, Shipping, and Session, projected from the same order\nform fetch. Method-agnostic: projects whatever payment systems VTEX returns, not\nonly credit card. Never carries a full card number or CVV.\n\"\"\"\ntype Payment implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order form ID.\n \"\"\"\n id: ID!\n\n \"\"\"\n Payment methods available on this cart. Empty when none are configured.\n \"\"\"\n methods: [PaymentMethod!]!\n\n \"\"\"\n The currently selected method's id (PaymentMethod.id), or null when unselected.\n \"\"\"\n selectedMethodId: String\n\n \"\"\"\n Saved-card account reference for the current selection, when a saved card is selected. Null otherwise.\n \"\"\"\n selectedAccountId: String\n\n \"\"\"\n The currently selected installment tier, resolved from the selection against installmentOptions. Null when unselected or unresolved.\n \"\"\"\n selectedInstallment: PaymentInstallment\n\n \"\"\"\n Installment plans available, grouped by method (and BIN, for card methods). Serves both a new card (matched by BIN) and a saved card (matched by its stored method).\n \"\"\"\n installmentOptions: [PaymentInstallmentOption!]!\n\n \"\"\"\n The shopper's saved cards. Empty for anonymous shoppers or when none are saved.\n \"\"\"\n savedCards: [SavedCard!]!\n\n \"\"\"\n The BIN persisted on the current selection, or null when the selection carries\n none (a non-card method, or a saved card selected without one). Echoes back what\n paymentMethodSelect wrote, which is what a client needs in order to know whether\n its own locally-detected BIN has actually reached the order form: installment\n options are matched against THIS value server-side, never against a client draft.\n Not sensitive — a BIN identifies an issuer range, never a card.\n \"\"\"\n selectedBin: String\n}\n\n\"\"\"\nA payment method available on the cart, with the server-driven validator config\nthat determines which card-form fields render, their masks, and whether they\nare required. A non-normalized value object (ADR-0012); read directly off\nPayment.methods.\n\"\"\"\ntype PaymentMethod {\n \"\"\"Payment-system identifier (VTEX paymentSystem stringId).\"\"\"\n id: String!\n\n \"\"\"\n Display name (e.g. \"Visa\", \"Mastercard\", \"Cash\"). Doubles as the network name for card methods.\n \"\"\"\n name: String!\n\n \"\"\"\n Payment-system group (e.g. \"creditCardPaymentGroup\", \"debitCardPaymentGroup\"). Store-configured; kept as a string, not an enum.\n \"\"\"\n groupName: String!\n\n \"\"\"\n Whether VTEX flags this payment system as requiring cardholder authentication\n (for example 3-D Secure). Projected verbatim from the order form's payment-system\n configuration: it describes how the store configured the system, not a guarantee\n about any particular transaction. Null when the order form carries no value for\n this system — which is not the same as false. Detection only: this API implements\n no authentication challenge.\n \"\"\"\n requiresAuthentication: Boolean\n\n \"\"\"\n Validator configuration driving field presence, masks, and requiredness.\n \"\"\"\n validator: PaymentMethodValidator!\n}\n\n\"\"\"\nPer-method validator configuration. Server-driven so no component hardcodes\nfield presence, masks, or requiredness.\n\"\"\"\ntype PaymentMethodValidator {\n \"\"\"\n Mask pattern for the card number input. Null when the method carries no mask.\n \"\"\"\n numberMask: String\n\n \"\"\"\n Regex for real-time network detection against the typed BIN. Null when unset.\n \"\"\"\n numberRegex: String\n\n \"\"\"\n Mask pattern for the security-code (CVV) input. Null when unused or unmasked.\n \"\"\"\n cvvMask: String\n\n \"\"\"Regex validating the security-code (CVV) input. Null when unused.\"\"\"\n cvvRegex: String\n\n \"\"\"Whether this method requires the security code (CVV).\"\"\"\n cvvRequired: Boolean!\n\n \"\"\"Whether this method requires an expiration date.\"\"\"\n expirationRequired: Boolean!\n\n \"\"\"Whether this method requires the cardholder name.\"\"\"\n holderNameRequired: Boolean!\n\n \"\"\"Whether this method requires a billing address.\"\"\"\n billingAddressRequired: Boolean!\n\n \"\"\"\n Whether a cardholder document field is displayable for this method. Distinct from documentRequired.\n \"\"\"\n documentDisplayable: Boolean!\n\n \"\"\"\n Whether the displayed cardholder document is required. Distinct from documentDisplayable — a document can be shown without being mandatory.\n \"\"\"\n documentRequired: Boolean!\n}\n\n\"\"\"\nOne installment tier: a specific installment count at a given per-installment\nvalue and total. Keyed by count within its parent PaymentInstallmentOption — a\nnon-normalized value object (ADR-0012).\n\"\"\"\ntype PaymentInstallment {\n \"\"\"Number of installments (1 = single payment, no interest).\"\"\"\n count: Int!\n\n \"\"\"Amount per installment.\"\"\"\n value: Money!\n\n \"\"\"Total amount across all installments, including any interest.\"\"\"\n total: Money!\n\n \"\"\"\n Interest rate in hundredths of a percent, exactly as the VTEX order form carries it (100 = 1%; 0 = interest-free).\n \"\"\"\n interestRate: Float!\n}\n\n\"\"\"\nInstallment tiers available for one payment method (and, for card methods, one\nBIN range). A non-normalized value object (ADR-0012): read straight off\nPayment.installmentOptions.\n\"\"\"\ntype PaymentInstallmentOption\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"The payment method this option applies to (PaymentMethod.id).\"\"\"\n methodId: String!\n\n \"\"\"\n The BIN range this option applies to, when method-and-BIN-specific. Null when the option applies by method alone.\n \"\"\"\n bin: String\n\n \"\"\"Base order value this option was quoted against.\"\"\"\n baseValue: Money!\n\n \"\"\"\n Installment tiers, with a guaranteed `count: 1` entry prepended when VTEX\n omits one. A tier that upstream already supplies is passed through untouched\n (including its position and interest rate).\n \"\"\"\n installments: [PaymentInstallment!]!\n}\n\n\"\"\"\nOne of the shopper's saved cards. Never carries a full card number or CVV. A\nnormalized, id-bearing value object (ADR-0012): id is a stable opaque cache\nkey, not a Relay Node.\n\"\"\"\ntype SavedCard\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Opaque, stable cache key. Not a Relay Node ID; SavedCard is not registered in the cross-module Node registry.\n \"\"\"\n id: ID!\n\n \"\"\"\n The saved-card account reference. Pass this back on selection; never a full card number.\n \"\"\"\n accountId: String!\n\n \"\"\"\n The saved card's payment-system id (PaymentMethod.id), for cross-referencing validator config and installment options.\n \"\"\"\n methodId: String!\n\n \"\"\"Card network / issuer display name (e.g. \"Visa\").\"\"\"\n network: String!\n\n \"\"\"Last four digits of the card number.\"\"\"\n lastFourDigits: String!\n\n \"\"\"BIN of the saved card, for installment-option lookup by network.\"\"\"\n bin: String!\n\n \"\"\"Shopper-assigned custom label, when set.\"\"\"\n label: String\n\n \"\"\"\n Whether the card is expired. Expired cards are still returned; non-selectable is a UI concern downstream.\n \"\"\"\n expired: Boolean!\n\n \"\"\"\n Whether completing payment with this card requires the shopper to enter the CVV.\n \"\"\"\n cvvRequired: Boolean!\n}\n\n\"\"\"\nInput for selecting a payment method, optionally identifying a new card (by\nBIN) or a saved card (by account reference). A non-card method (e.g. Cash)\nomits both `bin` and `accountId`. When `accountId` is given, `bin` may also\nbe given to match the saved card against a BIN-specific installment option;\na `bin` that does not match the saved card's own BIN is rejected with\nMISMATCHED_BIN_FOR_SAVED_CARD.\n\"\"\"\ninput PaymentMethodSelectInput {\n \"\"\"The method to select (PaymentMethod.id).\"\"\"\n methodId: String!\n\n \"\"\"\n A newly-entered card's BIN, once detected. Omit for a saved card or a non-card method.\n \"\"\"\n bin: String\n\n \"\"\"\n A saved card's account reference (SavedCard.accountId). Omit for a new card or a non-card method.\n \"\"\"\n accountId: String\n}\n\n\"\"\"Error codes for the paymentMethodSelect mutation.\"\"\"\nenum PaymentMethodSelectErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The provided methodId does not match any entry in Payment.methods.\"\"\"\n INVALID_METHOD_ID\n\n \"\"\"The provided accountId does not match any entry in Payment.savedCards.\"\"\"\n INVALID_SAVED_CARD_ACCOUNT_ID\n\n \"\"\"The bin does not match the BIN of the saved card named by accountId.\"\"\"\n MISMATCHED_BIN_FOR_SAVED_CARD\n\n \"\"\"\n The bin is not 1 to 8 numeric digits — a BIN identifies an issuer range, never a card number.\n \"\"\"\n INVALID_BIN_FORMAT\n\n \"\"\"\n Never returned. The input once allowed at most one of bin / accountId; both are\n now accepted so a saved card can be matched to an installment option by its own\n BIN. Retained per ADR-00010 (additive-only with a deprecation window).\n \"\"\"\n CONFLICTING_BIN_AND_ACCOUNT_ID @deprecated(reason: \"Never returned; bin and accountId may now be sent together.\") @deprecationDate(deletionDate: \"2027-08-06\")\n}\n\n\"\"\"A domain error returned from the paymentMethodSelect mutation.\"\"\"\ntype PaymentMethodSelectError {\n \"\"\"Machine-readable error code.\"\"\"\n code: PaymentMethodSelectErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the paymentMethodSelect mutation.\"\"\"\ntype PaymentMethodSelectPayload {\n \"\"\"The updated payment state, or null if errors occurred.\"\"\"\n payment: Payment\n\n \"\"\"\n The current billing address, echoed for convenience. Null when same-as-delivery is in effect.\n \"\"\"\n billingAddress: Address\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [PaymentMethodSelectError!]!\n}\n\n\"\"\"\nInput for selecting an installment count for the currently-selected method.\n\"\"\"\ninput PaymentInstallmentSelectInput {\n \"\"\"Number of installments to select (1 = single payment, no interest).\"\"\"\n count: Int!\n}\n\n\"\"\"Error codes for the paymentInstallmentSelect mutation.\"\"\"\nenum PaymentInstallmentSelectErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n No payment method has been selected yet (paymentMethodSelect was never called).\n \"\"\"\n MISSING_PAYMENT_METHOD_SELECTION\n\n \"\"\"\n The count does not match any tier in the resolvable installment option.\n \"\"\"\n INVALID_INSTALLMENT_COUNT\n}\n\n\"\"\"A domain error returned from the paymentInstallmentSelect mutation.\"\"\"\ntype PaymentInstallmentSelectError {\n \"\"\"Machine-readable error code.\"\"\"\n code: PaymentInstallmentSelectErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"\nPayload returned by the paymentInstallmentSelect mutation. Includes the\nrecomputed cart so the order total (with any interest) updates in the same\nround trip.\n\"\"\"\ntype PaymentInstallmentSelectPayload {\n \"\"\"The updated payment state, or null if errors occurred.\"\"\"\n payment: Payment\n\n \"\"\"\n The recomputed cart, or null if errors occurred. Carries the order total with any installment interest applied.\n \"\"\"\n cart: Cart\n\n \"\"\"\n The current billing address, echoed for convenience. Null when same-as-delivery is in effect.\n \"\"\"\n billingAddress: Address\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [PaymentInstallmentSelectError!]!\n}\n\n\"\"\"\nInput for setting the billing address. When sameAsDelivery is true, every\nother field is ignored and no upstream write happens (VTEX's own default:\nan absent invoiceData falls back to the delivery address).\n\"\"\"\ninput PaymentBillingAddressUpdateInput {\n \"\"\"\n When true, billing uses the delivery address; every field below is ignored.\n \"\"\"\n sameAsDelivery: Boolean!\n\n \"\"\"Address classification for the billing address.\"\"\"\n addressType: String\n\n \"\"\"\n Postal code for the billing address. Required when sameAsDelivery is false.\n \"\"\"\n postalCode: String\n\n \"\"\"\n ISO 3166-1 alpha-3 country code (e.g. \"BRA\"). Required when sameAsDelivery is false.\n \"\"\"\n country: String\n\n \"\"\"Street name.\"\"\"\n street: String\n\n \"\"\"Street number.\"\"\"\n number: String\n\n \"\"\"Additional address detail (apartment, suite, etc.).\"\"\"\n complement: String\n\n \"\"\"Neighborhood / district.\"\"\"\n neighborhood: String\n\n \"\"\"City name.\"\"\"\n city: String\n\n \"\"\"State / province code.\"\"\"\n state: String\n\n \"\"\"\n A landmark reference. Sent upstream but not echoed back on billingAddress (Decision 11).\n \"\"\"\n reference: String\n}\n\n\"\"\"Error codes for the paymentBillingAddressUpdate mutation.\"\"\"\nenum PaymentBillingAddressUpdateErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"sameAsDelivery is false but postalCode or country was omitted.\"\"\"\n MISSING_BILLING_ADDRESS_FIELDS\n}\n\n\"\"\"A domain error returned from the paymentBillingAddressUpdate mutation.\"\"\"\ntype PaymentBillingAddressUpdateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: PaymentBillingAddressUpdateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the paymentBillingAddressUpdate mutation.\"\"\"\ntype PaymentBillingAddressUpdatePayload {\n \"\"\"\n The billing address just written, or null when same-as-delivery is in effect.\n \"\"\"\n billingAddress: Address\n\n \"\"\"\n The updated payment state, or null if errors occurred, or when same-as-delivery is in effect, where no upstream write happens.\n \"\"\"\n payment: Payment\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [PaymentBillingAddressUpdateError!]!\n}\n\n\"\"\"\nError codes for the paymentStartTransaction mutation. Login-required VTEX\nbusiness codes (CHK0087, CHK003, ORD062) and generic infrastructure failures\nare never in this enum — per ADR-00007 they surface as top-level GraphQL\nerrors (UNAUTHORIZED / UPSTREAM_ERROR), not userErrors.\n\"\"\"\nenum PaymentStartTransactionErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n No payment method has been selected yet (paymentMethodSelect was never called).\n \"\"\"\n MISSING_PAYMENT_METHOD_SELECTION\n\n \"\"\"\n VTEX opened the transaction but named no merchant transaction on it, so\n neither the transaction id the send-payments POST addresses nor the merchant\n name its body carries exists. The transaction is open upstream but cannot be\n paid; the legacy checkout refuses the same state.\n \"\"\"\n INVALID_TRANSACTION_DATA\n}\n\n\"\"\"A domain error returned from the paymentStartTransaction mutation.\"\"\"\ntype PaymentStartTransactionError {\n \"\"\"Machine-readable error code.\"\"\"\n code: PaymentStartTransactionErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"\nPayload returned by paymentStartTransaction: everything the browser needs to\nrun the direct-to-gateway send-payments call (out of scope here) and the\nlater gatewayCallback. The transaction cookies VTEX issues are set on the\nHTTP response (Set-Cookie), not exposed as a field.\n\"\"\"\ntype PaymentStartTransactionPayload {\n \"\"\"The opened transaction's id. Null if errors occurred.\"\"\"\n transactionId: String\n\n \"\"\"\n Name of the merchant responsible for the sale, as the gateway call's payment\n `id` and `transaction.merchantName`. Projected from the same\n merchantTransactions entry transactionId comes from — VTEX returns one entry\n per seller, so a multi-seller order carries more than one and only the first\n is exposed here (see specs/payment-transaction-payload-gaps.md, Decision 1).\n Null if errors occurred.\n \"\"\"\n merchantName: String\n\n \"\"\"\n Origin of the payment gateway this transaction must be paid at, e.g.\n \"https://mystore.vtexpayments.com.br\" — the base the browser's\n direct-to-gateway send-payments POST appends its own path to. Origin only:\n VTEX's own receiver path is the split-payment endpoint, not the\n per-transaction one. Always an https origin inside vtexpayments.com.br: an\n upstream receiver URI that is neither is rejected in favour of the\n account-derived origin, since this field decides where card data is sent.\n Null if errors occurred.\n \"\"\"\n gatewayUrl: String\n\n \"\"\"\n The order group, needed for the gateway call's orderId parameter and for gatewayCallback's input.\n \"\"\"\n orderGroup: String\n\n \"\"\"The shopper's user profile id, needed for the gateway call.\"\"\"\n userProfileId: String\n\n \"\"\"\n The payment amount for this transaction, as a decimal (e.g. \"100.00\") for\n display. The gateway body wants minor units — read valueInCents for that,\n never a client-side multiplication of this amount.\n \"\"\"\n value: Money\n\n \"\"\"\n The same payment amount in the currency's minor units (e.g. 10000 for\n \"100.00\"), exactly as VTEX returned it and as this mutation sent it upstream.\n The unit the gateway body's `value`/`installmentValue` fields expect. Null if\n errors occurred.\n \"\"\"\n valueInCents: Int\n\n \"\"\"\n Pre-interest base amount this transaction was opened against, in the\n currency's minor units — the gateway body's `referenceValue`. Equals\n valueInCents when no interest applies. Null if errors occurred.\n \"\"\"\n referenceValueInCents: Int\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [PaymentStartTransactionError!]!\n}\n\n\"\"\"\nInput for finalizing a transaction after the direct-to-gateway call\nsucceeded.\n\"\"\"\ninput PaymentGatewayCallbackInput {\n \"\"\"The order group returned by paymentStartTransaction.\"\"\"\n orderGroup: String!\n}\n\n\"\"\"\nError codes for the paymentGatewayCallback mutation. Login-required VTEX\nbusiness codes (CHK0087, CHK003, ORD062) and generic infrastructure failures\nare never in this enum — per ADR-00007 they surface as top-level GraphQL\nerrors (UNAUTHORIZED / UPSTREAM_ERROR), not userErrors.\n\"\"\"\nenum PaymentGatewayCallbackErrorCode {\n \"\"\"\n The transaction cookies (Vtex_CHKO_Auth, CheckoutDataAccess) were not present on this request — paymentStartTransaction was not called first, or the five-minute window lapsed.\n \"\"\"\n MISSING_TRANSACTION_COOKIES\n\n \"\"\"\n VTEX requires a redirect or out-of-band flow to complete this payment. Phase 2; not supported in v1.\n \"\"\"\n REDIRECT_REQUIRED\n}\n\n\"\"\"A domain error returned from the paymentGatewayCallback mutation.\"\"\"\ntype PaymentGatewayCallbackError {\n \"\"\"Machine-readable error code.\"\"\"\n code: PaymentGatewayCallbackErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the paymentGatewayCallback mutation.\"\"\"\ntype PaymentGatewayCallbackPayload {\n \"\"\"\n The finalized order group, echoed back on success. Null if errors occurred.\n \"\"\"\n orderId: String\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [PaymentGatewayCallbackError!]!\n}\n\n\"\"\"\nThe installment plan a settled order was charged in. Projected from the\nplaced-order payment record, which carries the count and the settled total\nonly — never a per-installment amount and never an interest rate, unlike the\nlive order form behind `Payment.selectedInstallment`. That asymmetry is\nupstream and permanent (see specs/settle-and-project-the-placed-order.md,\nDecision 2).\n\"\"\"\ntype OrderInstallment\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Number of installments the order was charged in. 1 = single payment.\"\"\"\n count: Int!\n\n \"\"\"\n Total charged across all installments, including any interest. Null when the\n settled record carries no amount, or no currency to denominate it in.\n \"\"\"\n total: Money\n}\n\n\"\"\"\nA settled order's payment recap. Read-only; projected from VTEX's\nsettled-order source rather than the live order form, so it keeps resolving\nafter the order form itself stops existing. Carries no sensitive field: no\nfull card number or CVV is ever read for this projection (RFC D13).\n\"\"\"\ntype Order implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order group.\n \"\"\"\n id: ID!\n\n \"\"\"\n Card network / payment-system display name (e.g. \"Visa\", or a non-card\n method's own name such as \"Boleto Bancário\"). Null when the settled order\n carries no payment record to project one from.\n \"\"\"\n network: String\n\n \"\"\"\n Last four digits of the card used, when the payment method was a card.\n Present for both a saved and a freshly-entered card — unlike Payment (T1),\n where last four is a saved-card-only field. Null for a non-card method or\n when no payment record exists to project one from.\n \"\"\"\n lastFourDigits: String\n\n \"\"\"\n Shopper-assigned custom label for the saved card used, when one was set.\n Null for a freshly-entered (unsaved) card, a non-card method, or when no\n payment record exists to project one from (see specs/order-read-model.md,\n Decision 5).\n \"\"\"\n label: String\n\n \"\"\"\n The installment plan this order was charged in. Null when the settled record\n reports none.\n \"\"\"\n installment: OrderInstallment\n}\n\n\"\"\"\nThe authentication methods an account enables, read before rendering a sign-in form.\n\"\"\"\ntype SessionAuthProviders {\n \"\"\"Whether classic email and password sign-in is enabled.\"\"\"\n passwordAuthentication: Boolean!\n\n \"\"\"Whether email access-code (passwordless) sign-in is enabled.\"\"\"\n accessKeyAuthentication: Boolean!\n}\n\n\"\"\"\nA typed, expected failure of a login attempt. Distinct from the top-level\nUNAUTHORIZED gate: this is a domain outcome (the request was understood, the\nbusiness rejected it), never an infrastructure failure.\n\"\"\"\ntype SessionError {\n \"\"\"Machine-readable error code for the login-attempt failure.\"\"\"\n code: SessionErrorCode!\n\n \"\"\"Human-readable message describing the failure.\"\"\"\n message: String!\n}\n\n\"\"\"The set of login-attempt failure codes a session mutation can return.\"\"\"\nenum SessionErrorCode {\n \"\"\"Email or password was not accepted by VTEX ID.\"\"\"\n INVALID_CREDENTIALS\n\n \"\"\"The access code was wrong or expired.\"\"\"\n INVALID_ACCESS_CODE\n\n \"\"\"Too many access codes were requested for this email.\"\"\"\n ACCESS_CODE_SEND_LIMIT\n\n \"\"\"The user is blocked by VTEX ID.\"\"\"\n BLOCKED\n\n \"\"\"\n BFF-side guard: the request lacked the auth context it needed (e.g. a missing\n authenticationToken). Not a VTEX outcome.\n \"\"\"\n MISSING_AUTH_CONTEXT\n}\n\n\"\"\"\nThe login / logout result, projected from the one refreshed order form. session,\ncart, clientProfile and clientPreferences are normalized Relay Nodes (keyed on\nthe orderFormId) so Apollo merges them into the cache and the client updates\nreactively with no refetch.\n\"\"\"\ntype SessionLoginPayload {\n \"\"\"The session after the attempt; loggedIn is true only on success.\"\"\"\n session: Session!\n\n \"\"\"The cart projected from the refreshed order form.\"\"\"\n cart: Cart!\n\n \"\"\"\n The client profile projected from the refreshed order form; the entity always\n resolves, its fields are null when unset.\n \"\"\"\n clientProfile: ClientProfile!\n\n \"\"\"\n The client preferences projected from the refreshed order form; the entity\n always resolves, its fields are null when unset.\n \"\"\"\n clientPreferences: ClientPreferences!\n\n \"\"\"Typed login-attempt failures. Empty on success.\"\"\"\n userErrors: [SessionError!]!\n}\n\n\"\"\"The result of requesting an email access code.\"\"\"\ntype SessionRequestAccessCodePayload {\n \"\"\"\n Opaque token to hand back to sessionValidateAccessCode. Null when the request\n failed.\n \"\"\"\n authenticationToken: String\n\n \"\"\"Typed request failures. Empty on success.\"\"\"\n userErrors: [SessionError!]!\n}\n\n\"\"\"Credentials for a classic email + password sign-in.\"\"\"\ninput SessionPasswordLoginInput {\n \"\"\"The shopper's email address.\"\"\"\n email: String!\n\n \"\"\"\n The shopper's password. Proxied to VTEX ID over TLS, never logged or persisted.\n \"\"\"\n password: String!\n}\n\n\"\"\"The email to send an access code to.\"\"\"\ninput SessionRequestAccessCodeInput {\n \"\"\"The shopper's email address the access code is sent to.\"\"\"\n email: String!\n}\n\n\"\"\"An access code plus the authenticationToken it was issued against.\"\"\"\ninput SessionValidateAccessCodeInput {\n \"\"\"The shopper's email address.\"\"\"\n email: String!\n\n \"\"\"The access code the shopper received by email.\"\"\"\n accessCode: String!\n\n \"\"\"\n The opaque token returned by sessionRequestAccessCode, handed back unchanged.\n \"\"\"\n authenticationToken: String!\n}\n\n\"\"\"\nThe shopper's active region. Both fields are nullable: a shopper who has never\nsubmitted a postal code has no region, and a submitted postal code does not\nalways resolve to a known city.\n\"\"\"\ntype Region\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Stable identity for cache normalization. Not a Relay Node ID: a region is\n session-scoped shopper state, never addressable by global id.\n \"\"\"\n id: ID!\n\n \"\"\"The active postal code, formatted for display. Null when none is set.\"\"\"\n postalCode: String\n\n \"\"\"\n The city the postal code resolves to. Null when the postal code is unknown to\n the resolver, which is a normal state and not an error.\n \"\"\"\n city: String\n}\n\n\"\"\"\nWhy a region write was refused. Stable across locales, so a client can render\neach case in the shopper's language instead of echoing the server's message.\n\"\"\"\nenum RegionErrorCode {\n \"\"\"The value did not normalize to a complete postal code.\"\"\"\n INVALID_POSTAL_CODE\n\n \"\"\"The postal code is complete but falls outside the served ranges.\"\"\"\n UNSERVED_POSTAL_CODE\n}\n\n\"\"\"\nA typed, expected failure of a region write: the request was understood and the\nbusiness rejected it. Distinct from an infrastructure failure, which surfaces as\na top-level GraphQL error.\n\"\"\"\ntype RegionError {\n \"\"\"Which rule refused the postal code.\"\"\"\n code: RegionErrorCode!\n\n \"\"\"\n Human-readable message describing why the postal code was rejected. Written in\n English by the server; a client should key its copy on `code` instead.\n \"\"\"\n message: String!\n}\n\n\"\"\"The result of submitting a postal code.\"\"\"\ntype RegionUpdatePayload {\n \"\"\"\n The region after the write. Null when the postal code was rejected, leaving\n any previously active region untouched.\n \"\"\"\n region: Region\n\n \"\"\"Typed rejections. Empty on success.\"\"\"\n errors: [RegionError!]!\n}\n\n\"\"\"\nA shopper's persistent, cart-independent profile. Distinct from checkout's\norder-form-scoped ClientProfile.\n\"\"\"\ntype CustomerProfile\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Stable identity for cache normalization. Not a Relay Node ID: the profile is\n session-scoped shopper state, derived from the session and never addressable\n by a global id (no id argument, no Query.node entry — IDOR-safe).\n \"\"\"\n id: ID!\n\n \"\"\"Given name. Null when unset.\"\"\"\n firstName: String\n\n \"\"\"Family name. Null when unset.\"\"\"\n lastName: String\n\n \"\"\"\n The shopper's login identifier. Read-only — excluded from the update input by construction.\n \"\"\"\n email: String\n\n \"\"\"Primary contact phone. Null when unset.\"\"\"\n homePhone: String\n\n \"\"\"Mobile contact phone. Null when unset.\"\"\"\n mobilePhone: String\n\n \"\"\"National tax ID. Null when unset.\"\"\"\n document: String\n\n \"\"\"\n The tax ID's type, so the control can adapt to the shopper's region. Null when unset.\n \"\"\"\n documentType: String\n\n \"\"\"Date of birth. Null when unset.\"\"\"\n birthDate: String\n\n \"\"\"\n Free-form gender value; the store configures the options it offers. Null when unset.\n \"\"\"\n gender: String\n}\n\n\"\"\"A typed, expected failure of a profile write.\"\"\"\ntype CustomerProfileError {\n \"\"\"Machine-readable code for the write failure.\"\"\"\n code: CustomerProfileErrorCode!\n\n \"\"\"A safe message. Never echoes a submitted profile value.\"\"\"\n message: String!\n}\n\n\"\"\"The set of expected, typed profile-write failure codes.\"\"\"\nenum CustomerProfileErrorCode {\n \"\"\"\n The account's profile schema refused one or more submitted values. It does not\n attribute the refusal to a cause — a required field left empty and a malformed\n value are not distinguishable until the upstream rejection body is\n characterised, at which point a narrower code joins this one.\n \"\"\"\n INVALID_VALUE\n}\n\n\"\"\"\nEvery field is optional: provide only what the shopper edited. An empty string\nis an explicit erase; an omitted field is left untouched. A `null` is neither —\nit counts as not provided, so nulling an untouched field cannot wipe it.\n\"\"\"\ninput CustomerProfileUpdateInput {\n \"\"\"Given name to write. Omit to leave unchanged; empty string to erase.\"\"\"\n firstName: String\n\n \"\"\"Family name to write. Omit to leave unchanged; empty string to erase.\"\"\"\n lastName: String\n\n \"\"\"\n Primary contact phone to write. Omit to leave unchanged; empty string to erase.\n \"\"\"\n homePhone: String\n\n \"\"\"\n Mobile contact phone to write. Omit to leave unchanged; empty string to erase.\n \"\"\"\n mobilePhone: String\n\n \"\"\"\n National tax ID to write. Omit to leave unchanged; empty string to erase.\n \"\"\"\n document: String\n\n \"\"\"Tax ID type to write. Omit to leave unchanged; empty string to erase.\"\"\"\n documentType: String\n\n \"\"\"\n Date of birth to write. Omit to leave unchanged; empty string to erase.\n \"\"\"\n birthDate: String\n\n \"\"\"Gender value to write. Omit to leave unchanged; empty string to erase.\"\"\"\n gender: String\n}\n\n\"\"\"\nThe result of a profile write, carrying the merged profile and any typed\nfailures.\n\"\"\"\ntype CustomerProfileUpdatePayload {\n \"\"\"\n The profile after the write, so a reactive fragment re-delivers without a refetch.\n \"\"\"\n customerProfile: CustomerProfile!\n\n \"\"\"Typed, expected write failures. Empty on success.\"\"\"\n userErrors: [CustomerProfileError!]!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar federation__Scope\n\nscalar federation__Policy\n\nscalar federation__ContextFieldValue\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n";
|
|
18305
|
+
var core_federation_default = "# GENERATED by src/schema/emit.ts (pnpm run schema:emit) — do not edit by hand.\n# core version: 0.0.1\nschema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n mutation: Mutation\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.9\", import: [\"@key\"])\n\n\"\"\"\nRecords the planned removal date of a deprecated schema element. Pairs with the\nstandard built-in `@deprecated(reason)`: `@deprecated` carries the client-facing\nreason, `@deprecationDate` carries only the machine-readable deletion date. The\nreason is not duplicated here — that would drift from the built-in.\n\nThis replaces the previous redefinition of the built-in `@deprecated` (which\nadded a `deletionDate` argument). Redefining a built-in makes Federation\ncomposition (`wgc compose`) fail, so the deletion date moved to this custom\ndirective. As a non-built-in directive it is dropped during composition rather\nthan rejected. See ADR-00011 and CONTEXT.md. The `@deprecated`/`@deprecationDate`\npairing (both directions) and the future-date rule are enforced by a contract\ntest, not a lint rule.\n\"\"\"\ndirective @deprecationDate(\n \"\"\"\n Target removal date in ISO-8601 (YYYY-MM-DD) format. Must be in the future.\n \"\"\"\n deletionDate: String!\n) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE\n\n\"\"\"\nDeclares an HTTP cache policy for a type, field, or interface. Apollo Server\naggregates every hint resolved by a query and emits a single `Cache-Control`\nheader whose `max-age` is the minimum across hints and whose scope is the most\nrestrictive (PRIVATE wins over PUBLIC). Mutations and error responses ignore\nthese hints and always carry `Cache-Control: no-store`.\n\"\"\"\ndirective @cacheControl(\n \"\"\"Longest time (in seconds) this data may live in a cache.\"\"\"\n maxAge: Int\n\n \"\"\"\n Whether shared caches (CDN, proxy) may store the response. Defaults to\n PUBLIC when another field or type sets a scope and nothing overrides it.\n \"\"\"\n scope: CacheControlScope\n\n \"\"\"\n When true, this field inherits its `maxAge` from the parent field rather\n than contributing its own hint. Useful for wrapper/connection types.\n \"\"\"\n inheritMaxAge: Boolean\n) on OBJECT | FIELD_DEFINITION | INTERFACE\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!, label: String) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ndirective @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @federation__policy(policies: [[federation__Policy!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @federation__context(name: String!) repeatable on INTERFACE | OBJECT | UNION\n\ndirective @federation__fromContext(field: federation__ContextFieldValue) on ARGUMENT_DEFINITION\n\ndirective @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR\n\ndirective @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION\n\n\"\"\"Visibility scope for an HTTP cache entry.\"\"\"\nenum CacheControlScope {\n \"\"\"\n The response may be stored by shared caches (CDN, proxy) and reused\n across users.\n \"\"\"\n PUBLIC\n\n \"\"\"\n The response may only be stored by single-user caches (browser, service\n worker). Shared caches must not store it.\n \"\"\"\n PRIVATE\n}\n\n\"\"\"\nAn [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) encoded UTC datetime.\n\"\"\"\nscalar DateTime\n\n\"\"\"\nAn [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) encoded URI.\n\"\"\"\nscalar URI\n\n\"\"\"A string containing raw HTML markup.\"\"\"\nscalar HTML\n\n\"\"\"\nAn ISO 4217 currency amount represented as a decimal string (e.g. \"29.99\").\n\"\"\"\nscalar Decimal\n\n\"\"\"\nA [BCP 47](https://tools.ietf.org/html/bcp47) language tag (e.g. \"en-US\").\n\"\"\"\nscalar Locale\n\n\"\"\"An opaque cursor used for Relay-style pagination.\"\"\"\nscalar Cursor\n\n\"\"\"\nThe Relay `Node` interface. Every entity in the graph implements this so\nthe client can refetch any object with a single `node(id: ID!)` query.\n\"\"\"\ninterface Node {\n \"\"\"Globally unique identifier. Base64 of `entityType:rawId`.\"\"\"\n id: ID!\n}\n\n\"\"\"Metadata about the current page in a Relay connection.\"\"\"\ntype PageInfo {\n \"\"\"Whether more pages exist after the current page.\"\"\"\n hasNextPage: Boolean!\n\n \"\"\"Whether more pages exist before the current page.\"\"\"\n hasPreviousPage: Boolean!\n\n \"\"\"Cursor of the first edge in the current page.\"\"\"\n startCursor: Cursor\n\n \"\"\"Cursor of the last edge in the current page.\"\"\"\n endCursor: Cursor\n}\n\n\"\"\"Sort direction for orderBy arguments.\"\"\"\nenum SortDirection {\n \"\"\"Ascending order.\"\"\"\n ASC\n\n \"\"\"Descending order.\"\"\"\n DESC\n}\n\n\"\"\"Represents a monetary amount with its currency.\"\"\"\ntype Money {\n \"\"\"Decimal amount (e.g. \"49.99\").\"\"\"\n amount: Decimal!\n\n \"\"\"ISO 4217 currency code (e.g. \"USD\", \"BRL\").\"\"\"\n currencyCode: CurrencyCode!\n}\n\n\"\"\"A price range, useful for showing \"from – to\" on product cards.\"\"\"\ntype PriceRange {\n \"\"\"Lowest price across all variants.\"\"\"\n min: Money!\n\n \"\"\"Highest price across all variants.\"\"\"\n max: Money!\n}\n\n\"\"\"ISO 4217 currency codes. Extend as needed.\"\"\"\nenum CurrencyCode {\n \"\"\"United States Dollar.\"\"\"\n USD\n\n \"\"\"Euro.\"\"\"\n EUR\n\n \"\"\"British Pound Sterling.\"\"\"\n GBP\n\n \"\"\"Brazilian Real.\"\"\"\n BRL\n\n \"\"\"Argentine Peso.\"\"\"\n ARS\n\n \"\"\"Chilean Peso.\"\"\"\n CLP\n\n \"\"\"Colombian Peso.\"\"\"\n COP\n\n \"\"\"Mexican Peso.\"\"\"\n MXN\n\n \"\"\"Peruvian Sol.\"\"\"\n PEN\n\n \"\"\"Japanese Yen.\"\"\"\n JPY\n}\n\n\"\"\"An image resource associated with a product variant or other entity.\"\"\"\ntype Image {\n \"\"\"Source URL of the image.\"\"\"\n url: URI!\n\n \"\"\"Alt text for accessibility.\"\"\"\n altText: String\n}\n\n\"\"\"A video resource associated with a product.\"\"\"\ntype Video {\n \"\"\"Playback URL.\"\"\"\n url: URI!\n\n \"\"\"Alt text for accessibility.\"\"\"\n altText: String\n\n \"\"\"Preview / poster image.\"\"\"\n previewImage: Image\n}\n\n\"\"\"Any media attached to a product — image or video.\"\"\"\nunion Media = Image | Video\n\n\"\"\"Search engine optimization metadata for any displayable entity.\"\"\"\ntype SEO {\n \"\"\"Page title (`<title>` tag).\"\"\"\n title: String\n\n \"\"\"Meta description.\"\"\"\n description: String\n}\n\n\"\"\"Root query type for the storefront API.\"\"\"\ntype Query {\n \"\"\"\n Fetch any entity by its global ID.\n Clients use this for refetching or cache normalization.\n \"\"\"\n node(\n \"\"\"The globally unique ID.\"\"\"\n id: ID!\n ): Node\n\n \"\"\"Fetch multiple entities by their global IDs.\"\"\"\n nodes(\n \"\"\"A list of globally unique IDs.\"\"\"\n ids: [ID!]!\n ): [Node]!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nextend type Query {\n \"\"\"Look up a single product by global ID.\"\"\"\n product(\n \"\"\"Global product ID.\"\"\"\n id: ID!\n ): Product\n\n \"\"\"Look up a single product by its URL slug.\"\"\"\n productBySlug(\n \"\"\"URL-friendly handle (e.g. \\\"nike-zoom-janoski-red\\\").\"\"\"\n slug: String!\n ): Product\n\n \"\"\"Paginated, filterable list of products.\"\"\"\n products(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: ProductSortingInput\n\n \"\"\"Filter criteria.\"\"\"\n filter: ProductsFilterInput\n ): ProductsConnection!\n\n \"\"\"\n Full-text product search with facets and filters.\n Returns the same connection shape but is backed by the search engine\n rather than the catalog database.\n \"\"\"\n productSearch(\n \"\"\"Search query string.\"\"\"\n query: String!\n\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: ProductSortingInput\n\n \"\"\"Filter criteria.\"\"\"\n filter: ProductsFilterInput\n ): ProductSearchResult!\n\n \"\"\"\n Product recommendations based on a seed product (frequently bought\n together, similar items, etc.).\n \"\"\"\n productRecommendations(\n \"\"\"URL slug of the product to base recommendations on.\"\"\"\n slug: String!\n\n \"\"\"The recommendation strategy to use.\"\"\"\n intent: RecommendationIntent\n\n \"\"\"Maximum number of recommendations.\"\"\"\n first: Int = 10\n ): [Product!]!\n\n \"\"\"\n Resolves a catalog route by ID or by path and returns its confirmed localized equivalents.\n Null when the route does not exist or the catalog could not be reached.\n \"\"\"\n catalogRoute(\n \"\"\"The route to resolve, by ID or by path.\"\"\"\n input: CatalogRouteInput!\n ): CatalogRoute\n\n \"\"\"Look up a single category by global ID.\"\"\"\n category(\n \"\"\"Global category ID.\"\"\"\n id: ID!\n ): Category\n\n \"\"\"Look up a category by its URL slug.\"\"\"\n categoryBySlug(\n \"\"\"URL-friendly slug.\"\"\"\n slug: String!\n ): Category\n\n \"\"\"\n The full category tree starting from root departments.\n Accepts a `maxDepth` to limit tree traversal (default: all levels).\n \"\"\"\n categoryTree(\n \"\"\"Maximum depth of the tree to return. Maps to VTEX `categoryLevels`.\"\"\"\n maxDepth: Int\n ): [Category!]!\n\n \"\"\"Paginated flat list of categories.\"\"\"\n categories(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: CategorySortingInput\n ): CategoriesConnection!\n\n \"\"\"Look up a single brand by global ID.\"\"\"\n brand(\n \"\"\"Global brand ID.\"\"\"\n id: ID!\n ): Brand\n\n \"\"\"\n All brands available in the store, derived from the search index.\n Returns brands with their product counts.\n \"\"\"\n brands(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): BrandsConnection!\n\n \"\"\"Look up a single sales channel by global ID.\"\"\"\n salesChannel(\n \"\"\"Global sales channel ID.\"\"\"\n id: ID!\n ): SalesChannel\n\n \"\"\"\n Autocomplete suggestions for a partial search query.\n Returns suggested terms with estimated result counts and facet attributes.\n \"\"\"\n autocompleteSuggestions(\n \"\"\"Partial search query (e.g. \\\"choc\\\").\"\"\"\n query: String!\n ): [AutocompleteSuggestion!]!\n\n \"\"\"The most popular search terms in the last 14 days.\"\"\"\n topSearches: [TrendingSearch!]!\n\n \"\"\"\n Spelling correction for a potentially misspelled search query.\n Returns null if no correction is available.\n \"\"\"\n searchCorrection(\n \"\"\"The potentially misspelled query.\"\"\"\n query: String!\n ): SearchCorrection\n}\n\nextend type Query {\n \"\"\"\n Resolve the active cart from the request context (x-order-form-id header,\n then cookie). When no cart context is present, a new cart is created and\n the x-order-form-id cookie is set on the response.\n \"\"\"\n cart: Cart!\n}\n\nextend type Query {\n \"\"\"\n Simulate available shipping options for a variant on a product detail page.\n Returns delivery options with estimated times and prices for the given\n postal code and item.\n \"\"\"\n shippingSimulation(\n \"\"\"Simulation input: destination, SKU, quantity, and seller.\"\"\"\n input: ShippingSimulateInput!\n ): ShippingSimulationPayload!\n\n \"\"\"\n Simulate shipping options for the entire active cart. Reads the cart from the\n x-order-form-id context and simulates all of its items against the\n destination, returning the options common to every item with prices summed\n across items. Never mints a cart: absent context returns a MISSING_CART_CONTEXT\n userError.\n \"\"\"\n cartShippingSimulation(\n \"\"\"Simulation input: destination only. Items come from the active cart.\"\"\"\n input: CartShippingSimulateInput!\n ): CartShippingSimulationPayload!\n}\n\nextend type Query {\n \"\"\"\n The shopper's profile for the active cart, resolved from the same order form\n as `cart`. The entity always resolves; its fields are null when unset.\n \"\"\"\n clientProfile: ClientProfile!\n\n \"\"\"\n The shopper's preferences for the active cart, resolved from the same order\n form as `cart`. The entity always resolves; its fields are null when unset.\n \"\"\"\n clientPreferences: ClientPreferences!\n}\n\nextend type Query {\n \"\"\"\n The cart's shipping state. Projected from the same order form as Query.cart.\n \"\"\"\n shipping: Shipping!\n\n \"\"\"\n Resolve the shopper's session state from the active order form (x-order-form-id\n header, then cookie). Projects from the same order form fetch as `cart`.\n \"\"\"\n session: Session!\n}\n\nextend type Query {\n \"\"\"\n The cart's payment state: methods, selection, installment options, and saved cards. Projected from the same order form as Query.cart. Always resolves; lists are empty and selection fields are null when payment is unset.\n \"\"\"\n payment: Payment!\n}\n\nextend type Query {\n \"\"\"\n A settled order's payment recap, keyed on the order group\n paymentStartTransaction / paymentGatewayCallback return. Null when the\n order group does not resolve to a settled order — never a fabricated empty\n projection (contrast Query.payment, which always resolves).\n \"\"\"\n order(\n \"\"\"\n The order group returned by paymentStartTransaction / paymentGatewayCallback.\n \"\"\"\n orderGroup: String!\n ): Order\n}\n\nextend type Query {\n \"\"\"\n The fields a postal code resolves to. Needs no cart and no shopper. Null when\n the platform does not know the code. A lookup that could not run is an error,\n never null. A malformed argument is a BAD_USER_INPUT error and reaches no\n upstream.\n \"\"\"\n addressByPostalCode(\n \"\"\"\n ISO 3166-1 alpha-3 code, such as `BRA`. Case and surrounding spaces are\n ignored.\n \"\"\"\n country: String!\n\n \"\"\"\n The postal code, masked or not: letters, digits, spaces, and hyphens, up to\n 20 characters.\n \"\"\"\n postalCode: String!\n ): PostalCodeAddress\n}\n\nextend type Query {\n \"\"\"Which authentication methods this account enables.\"\"\"\n sessionAuthProviders: SessionAuthProviders!\n}\n\nextend type Query {\n \"\"\"\n The shopper's active region, or null when none has been set. Takes no\n arguments by design: the postal code travels on the transport.\n \"\"\"\n region: Region\n}\n\nextend type Query {\n \"\"\"\n The signed-in shopper's own persistent profile. Takes no id — the identity\n comes from the session. Nullable because it is permission-gated (ADR-00005).\n Null with no error when the session names no shopper. Null with an\n UNAUTHORIZED, UPSTREAM_ERROR, UPSTREAM_TIMEOUT or CIRCUIT_OPEN error on this\n field's path when the read fails (ADR-00007), so the rest of the operation\n still resolves. A profile whose fields are null when the shopper has saved\n nothing yet.\n \"\"\"\n customerProfile: CustomerProfile\n\n \"\"\"\n The signed-in shopper's saved addresses, in the order the profile returns\n them. Takes no id: the identity comes from the session. Empty when the shopper\n has none. Throws UNAUTHORIZED or UPSTREAM_ERROR (ADR-00007).\n \"\"\"\n customerAddresses: [CustomerAddress!]!\n\n \"\"\"\n The signed-in shopper's address book. Takes no id: the identity comes from the\n session. Throws UNAUTHORIZED, or UPSTREAM_ERROR when `addresses` is selected\n (ADR-00007).\n \"\"\"\n customerAddressBook: CustomerAddressBook!\n\n \"\"\"\n One of the signed-in shopper's saved addresses. Null when the id is malformed,\n names another entity type, or is not one of this shopper's addresses; these\n cases are indistinguishable.\n \"\"\"\n customerAddress(\n \"\"\"The CustomerAddress global id, as returned by `customerAddresses`.\"\"\"\n id: ID!\n ): CustomerAddress\n}\n\nextend type Query {\n \"\"\"\n The countries the store ships to. Takes no arguments and needs no cart or\n shopper. Null when the platform could not be read, with the reason in\n `errors`; a consumer then knows no shipping country rather than none.\n \"\"\"\n shippingCountries: ShippingCountries\n}\n\nextend type Query {\n \"\"\"\n Read one order owned by the signed-in shopper. A missing or foreign order returns null.\n \"\"\"\n customerOrder(\n \"\"\"Shopper-facing order number, or the global Node id of a CustomerOrder.\"\"\"\n id: ID!\n ): CustomerOrder @cacheControl(maxAge: 0, scope: PRIVATE)\n\n \"\"\"One page of the signed-in shopper's orders.\"\"\"\n customerOrders(\n \"\"\"Page size. Must be 25.\"\"\"\n first: Int!\n\n \"\"\"Opaque resume cursor issued by this field.\"\"\"\n after: Cursor\n\n \"\"\"Not supported. The list is read forward from the route.\"\"\"\n last: Int\n\n \"\"\"Not supported. The list is read forward from the route.\"\"\"\n before: Cursor\n\n \"\"\"View, text, and placed-date range.\"\"\"\n filter: CustomerOrdersFilterInput\n ): CustomerOrdersConnection! @cacheControl(maxAge: 0, scope: PRIVATE)\n}\n\nextend type Query {\n \"\"\"\n The signed-in shopper's own saved cards. Takes no id — the identity comes from\n the session. Resolves to the shopper's entity, with an empty list when no card\n is saved. Null only when the read fails, alongside an UNAUTHORIZED,\n UPSTREAM_ERROR, or UPSTREAM_TIMEOUT error at this path (ADR-00007), so the\n failure does not null the rest of the operation.\n \"\"\"\n customerSavedCards: CustomerSavedCards\n}\n\nextend type Query {\n \"\"\"\n The credit-card networks the store accepts on the request's sales channel,\n with their validation rules, for adding a saved card. Requires a signed-in\n shopper. Null when the read fails, alongside an UNAUTHORIZED, UPSTREAM_ERROR,\n UPSTREAM_TIMEOUT, or NO_CARD_NETWORKS error at this path (ADR-00007).\n NO_CARD_NETWORKS means the store accepts no credit-card network on this\n sales channel; the list is never empty.\n \"\"\"\n savedCardNetworks: [CardNetworkRule!]\n\n \"\"\"\n The signed-in shopper's saved addresses a new card can be billed to, in the\n platform's order. Takes no id. Empty when none is saved. Null only when the\n read fails, alongside an UNAUTHORIZED, UPSTREAM_ERROR, or UPSTREAM_TIMEOUT\n error at this path.\n \"\"\"\n customerSavedCardBillingAddresses: [CardBillingAddress!]\n}\n\n\"\"\"\nRoot mutation type. Cart and checkout operations are declared via\n`extend type Mutation` in the checkout module.\n\"\"\"\ntype Mutation {\n \"\"\"\n Exists only so `extend type Mutation` in feature modules is valid for schema\n linters and subgraph composition; not intended for client use.\n \"\"\"\n inertMutationPlaceholder: Boolean\n\n \"\"\"Placeholder field — removed once a real mutation is added via extend.\"\"\"\n empty: String @deprecated(reason: \"Placeholder — will be dropped when the first real mutation is added.\") @deprecationDate(deletionDate: \"2026-12-31\")\n}\n\nextend type Mutation {\n \"\"\"\n Add an item to the cart. If the variant already exists in the cart, its\n quantity is increased by the given amount.\n \"\"\"\n cartItemAdd(\n \"\"\"Item details to add.\"\"\"\n input: CartItemAddInput!\n ): CartItemAddPayload!\n\n \"\"\"Remove a line from the cart entirely (sets quantity to 0).\"\"\"\n cartItemRemove(\n \"\"\"Item to remove.\"\"\"\n input: CartItemRemoveInput!\n ): CartItemRemovePayload!\n\n \"\"\"\n Remove multiple items from the cart in a single upstream write (each line's\n quantity is set to 0). Tolerant: lines absent from the cart are skipped.\n \"\"\"\n cartItemsRemove(\n \"\"\"Items to remove.\"\"\"\n input: CartItemsRemoveInput!\n ): CartItemsRemovePayload!\n\n \"\"\"Increase the quantity of an existing cart item.\"\"\"\n cartItemQuantityIncrease(\n \"\"\"Item and quantity details.\"\"\"\n input: CartItemQuantityIncreaseInput!\n ): CartItemQuantityIncreasePayload!\n\n \"\"\"\n Decrease the quantity of an existing cart item.\n If the resulting quantity would be zero or less, the item is removed.\n \"\"\"\n cartItemQuantityDecrease(\n \"\"\"Item and quantity details.\"\"\"\n input: CartItemQuantityDecreaseInput!\n ): CartItemQuantityDecreasePayload!\n\n \"\"\"\n Apply a coupon code to the active cart. Appends to the existing set and returns\n the recomputed cart, or typed errors when the code is rejected.\n \"\"\"\n cartCouponApply(\n \"\"\"Coupon code to apply.\"\"\"\n input: CartCouponApplyInput!\n ): CartCouponApplyPayload!\n\n \"\"\"\n Remove a coupon code from the active cart. Idempotent when the code is not applied.\n \"\"\"\n cartCouponRemove(\n \"\"\"Coupon code to remove.\"\"\"\n input: CartCouponRemoveInput!\n ): CartCouponRemovePayload!\n\n \"\"\"\n Re-check the active cart against the platform and return it with each line's\n availability refreshed. Writes nothing: the cart is re-simulated and only\n `items.variant.availability` is corrected, so a line the platform can no longer\n fulfil comes back flagged for the shopper to remove. Quantities are never\n altered, and totals stay as the cart reported them until that removal\n recomputes them. Takes no arguments: the cart is resolved from the request\n context and is never created.\n \"\"\"\n cartValidate: CartValidatePayload!\n\n \"\"\"\n Move the active cart onto the request's sales channel, then reconcile its locale. Takes no\n arguments: the cart is resolved from the request context and is never created. An empty cart, or\n a cart already on the active channel, does not send `sc`. Otherwise one POST to `items/update`\n with `sc` re-sends every item's index and current quantity, so Checkout reprocesses that order\n form and drops lines the destination cannot sell. An upstream failure is reported in `userErrors`\n and does not throw.\n \"\"\"\n cartSalesChannelSync: CartSalesChannelSyncPayload!\n}\n\nextend type Mutation {\n \"\"\"\n Write the provided profile fields to the active cart's clientProfileData\n attachment. Partial: omitted fields are untouched. Never mints a cart.\n \"\"\"\n clientProfileUpdate(\n \"\"\"Profile fields to write.\"\"\"\n input: ClientProfileUpdateInput!\n ): ClientProfileUpdatePayload!\n\n \"\"\"\n Write the provided preference flags to the active cart's clientPreferencesData\n attachment. Partial: omitted flags are untouched. Never mints a cart.\n \"\"\"\n clientPreferencesUpdate(\n \"\"\"Preference flags to write.\"\"\"\n input: ClientPreferencesUpdateInput!\n ): ClientPreferencesUpdatePayload!\n\n \"\"\"\n Set the full delivery address (delivery step). Auto-selects the cheapest SLA\n when available. Never mints a cart.\n \"\"\"\n shippingAddressUpdate(\n \"\"\"Address details to write.\"\"\"\n input: ShippingAddressUpdateInput!\n ): ShippingAddressUpdatePayload!\n\n \"\"\"\n Select one shipping option for the whole cart. Fans the selection out to\n every item. Never mints a cart.\n \"\"\"\n shippingOptionSelect(\n \"\"\"The option to select.\"\"\"\n input: ShippingOptionSelectInput!\n ): ShippingOptionSelectPayload!\n}\n\nextend type Mutation {\n \"\"\"\n Select a payment method, optionally identifying a new card's BIN or a saved card's account.\n \"\"\"\n paymentMethodSelect(\n \"\"\"Method (and optional BIN/saved-card) to select.\"\"\"\n input: PaymentMethodSelectInput!\n ): PaymentMethodSelectPayload!\n\n \"\"\"\n Select an installment count for the currently-selected method. Recomputes the cart total.\n \"\"\"\n paymentInstallmentSelect(\n \"\"\"Installment count to select.\"\"\"\n input: PaymentInstallmentSelectInput!\n ): PaymentInstallmentSelectPayload!\n\n \"\"\"Set the billing address, or fall back to the delivery address.\"\"\"\n paymentBillingAddressUpdate(\n \"\"\"\n Billing address details, or sameAsDelivery: true to use the delivery address.\n \"\"\"\n input: PaymentBillingAddressUpdateInput!\n ): PaymentBillingAddressUpdatePayload!\n\n \"\"\"\n Open a VTEX transaction from the currently-selected payment. No input: every field is server-derived.\n \"\"\"\n paymentStartTransaction: PaymentStartTransactionPayload!\n\n \"\"\"\n Finalize a transaction after the direct-to-gateway payment call succeeded.\n \"\"\"\n paymentGatewayCallback(\n \"\"\"The order group to finalize.\"\"\"\n input: PaymentGatewayCallbackInput!\n ): PaymentGatewayCallbackPayload!\n}\n\nextend type Mutation {\n \"\"\"\n Sign in with email and password. Returns the refreshed session, or typed\n userErrors.\n \"\"\"\n sessionLoginWithPassword(\n \"\"\"The email and password to authenticate with.\"\"\"\n input: SessionPasswordLoginInput!\n ): SessionLoginPayload!\n\n \"\"\"\n Request an email access code. Returns an opaque authenticationToken to\n round-trip into validate.\n \"\"\"\n sessionRequestAccessCode(\n \"\"\"The email to send the access code to.\"\"\"\n input: SessionRequestAccessCodeInput!\n ): SessionRequestAccessCodePayload!\n\n \"\"\"\n Validate an email access code with its authenticationToken. Returns the\n refreshed session.\n \"\"\"\n sessionValidateAccessCode(\n \"\"\"The email, access code, and authenticationToken to validate.\"\"\"\n input: SessionValidateAccessCodeInput!\n ): SessionLoginPayload!\n\n \"\"\"\n Sign out. Clears auth cookies and refreshes the order form to a guest session.\n Idempotent.\n \"\"\"\n sessionLogout: SessionLoginPayload!\n}\n\nextend type Mutation {\n \"\"\"\n Set the shopper's active region from a postal code. Accepts the value as the\n shopper typed it, including any mask; the server normalizes it and derives the\n city.\n \"\"\"\n regionUpdate(\n \"\"\"The postal code to activate, masked or bare.\"\"\"\n postalCode: String!\n ): RegionUpdatePayload!\n}\n\nextend type Mutation {\n \"\"\"\n Partially update the signed-in shopper's own profile. Upserts: succeeds whether or not a record exists.\n \"\"\"\n customerProfileUpdate(\n \"\"\"The fields to write. Provide only what the shopper edited.\"\"\"\n input: CustomerProfileUpdateInput!\n ): CustomerProfileUpdatePayload!\n\n \"\"\"\n Save a new address for the signed-in shopper. The store must ship to the country, and the\n country's rules must accept the fields. Refusals come back as `userErrors`.\n \"\"\"\n customerAddressCreate(\n \"\"\"The address to save.\"\"\"\n input: CustomerAddressCreateInput!\n ): CustomerAddressCreatePayload!\n\n \"\"\"\n Change one of the signed-in shopper's saved addresses. The merged address is validated\n against its stored country. Refusals come back as `userErrors`.\n \"\"\"\n customerAddressUpdate(\n \"\"\"The CustomerAddress global id, as returned by `customerAddresses`.\"\"\"\n id: ID!\n\n \"\"\"The fields to change. Provide only what the shopper edited.\"\"\"\n input: CustomerAddressUpdateInput!\n ): CustomerAddressUpdatePayload!\n\n \"\"\"\n Remove one of the signed-in shopper's saved addresses. An address that is already gone\n counts as removed.\n \"\"\"\n customerAddressDelete(\n \"\"\"The CustomerAddress global id, as returned by `customerAddresses`.\"\"\"\n id: ID!\n ): CustomerAddressDeletePayload!\n}\n\nextend type Mutation {\n \"\"\"\n Request the cancellation of one order owned by the signed-in shopper. Checkout\n records the request and OMS applies it afterwards, so the returned order can\n still show the previous status.\n \"\"\"\n customerOrderCancel(\n \"\"\"Shopper-facing order number.\"\"\"\n id: ID!\n\n \"\"\"The optional reason.\"\"\"\n input: CustomerOrderCancelInput!\n ): CustomerOrderCancelPayload!\n}\n\nextend type Mutation {\n \"\"\"\n Removes one of the signed-in shopper's saved cards, by the id the\n customerSavedCards read returned. Returns the refreshed list on success.\n Throws UNAUTHORIZED / UPSTREAM_ERROR / UPSTREAM_TIMEOUT (ADR-00007).\n \"\"\"\n customerSavedCardRemove(\n \"\"\"The card's id, as customerSavedCards returned it.\"\"\"\n id: ID!\n ): CustomerSavedCardRemovePayload!\n}\n\n\"\"\"\nA manufacturer or designer brand.\nMaps to VTEX Brand entity.\n\"\"\"\ntype Brand implements Node\n @cacheControl(maxAge: 300, scope: PUBLIC)\n{\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Human-readable brand name.\"\"\"\n name: String!\n\n \"\"\"URL-friendly slug.\"\"\"\n slug: String!\n\n \"\"\"Whether the brand is currently active in the storefront.\"\"\"\n isActive: Boolean!\n\n \"\"\"Brand logo image.\"\"\"\n logo: Image\n\n \"\"\"SEO metadata.\"\"\"\n seo: SEO\n\n \"\"\"Paginated list of products belonging to this brand.\"\"\"\n products(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: ProductSortingInput\n\n \"\"\"Filter criteria.\"\"\"\n filter: ProductsFilterInput\n ): ProductsConnection!\n}\n\n\"\"\"Paginated list of brands.\"\"\"\ntype BrandsConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [BrandEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Total number of brands matching the query.\"\"\"\n totalCount: Int!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype BrandEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Brand!\n}\n\n\"\"\"\nA hierarchical product classification level (department → category → subcategory).\nMaps to VTEX Category entity. The tree structure is modeled via parent/children\nreferences, allowing arbitrary depth.\n\"\"\"\ntype Category implements Node\n @cacheControl(maxAge: 300, scope: PUBLIC)\n{\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Display name of the category.\"\"\"\n name: String!\n\n \"\"\"URL-friendly slug.\"\"\"\n slug: String!\n\n \"\"\"Full materialized path, e.g. \\\"/clothing/shirts/casual\\\".\"\"\"\n path: String!\n\n \"\"\"Zero-indexed depth in the tree (0 = department).\"\"\"\n level: Int!\n\n \"\"\"Whether this category is currently active.\"\"\"\n isActive: Boolean!\n\n \"\"\"Parent category. Null for top-level departments.\"\"\"\n parent: Category\n\n \"\"\"Direct children of this category.\"\"\"\n children: [Category!]!\n\n \"\"\"SEO metadata.\"\"\"\n seo: SEO\n\n \"\"\"Paginated products within this category.\"\"\"\n products(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: ProductSortingInput\n\n \"\"\"Filter criteria.\"\"\"\n filter: ProductsFilterInput\n ): ProductsConnection!\n\n \"\"\"Specification groups relevant to this category.\"\"\"\n specificationGroups: [SpecificationGroup!]!\n}\n\n\"\"\"Paginated list of categories.\"\"\"\ntype CategoriesConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [CategoryEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Total number of categories matching the query.\"\"\"\n totalCount: Int!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype CategoryEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Category!\n}\n\n\"\"\"\nA group of related specifications that apply to products in a category.\nMaps to VTEX Specification Group.\n\"\"\"\ntype SpecificationGroup {\n \"\"\"Display name (e.g. \\\"Physical Attributes\\\", \\\"Technical Details\\\").\"\"\"\n name: String!\n\n \"\"\"Specifications in this group.\"\"\"\n specifications: [Specification!]!\n}\n\n\"\"\"\nA product-level or variant-level specification field.\nMaps to VTEX Specification / Specification Field.\nExamples: \"Material\", \"Warranty Period\" (product-level);\n\"Color\", \"Size\" (variant-level / option axis).\n\"\"\"\ntype Specification {\n \"\"\"Display label (e.g. \\\"Material\\\").\"\"\"\n name: String!\n\n \"\"\"The assigned values for this specification.\"\"\"\n values: [String!]!\n\n \"\"\"How this spec was captured.\"\"\"\n fieldType: SpecificationFieldType!\n}\n\n\"\"\"A single breadcrumb entry in the product's navigation trail.\"\"\"\ntype BreadcrumbItem {\n \"\"\"Human-readable label (e.g. \"Electronics\", \"Smartphones\").\"\"\"\n name: String!\n\n \"\"\"URL path for this breadcrumb level (e.g. \"/electronics/smartphones\").\"\"\"\n url: String!\n\n \"\"\"1-based position in the breadcrumb trail.\"\"\"\n position: Int!\n}\n\n\"\"\"\nDiscriminates how a specification value was captured.\nMaps to VTEX FieldType numeric codes.\n\"\"\"\nenum SpecificationFieldType {\n \"\"\"Free text.\"\"\"\n TEXT\n\n \"\"\"Multi-line free text.\"\"\"\n MULTI_LINE_TEXT\n\n \"\"\"Single-select dropdown.\"\"\"\n COMBO\n\n \"\"\"Single-select radio.\"\"\"\n RADIO\n\n \"\"\"Multi-select checkbox.\"\"\"\n CHECKBOX\n\n \"\"\"Rich text / HTML.\"\"\"\n RICH_TEXT\n}\n\n\"\"\"\nA named option axis on a product that differentiates variants.\nThink \"Color\" or \"Size\". Each option has a set of possible values.\nSimilar to Shopify's ProductOption.\n\"\"\"\ntype ProductOption {\n \"\"\"Option name (e.g. \\\"Color\\\").\"\"\"\n name: String!\n\n \"\"\"\n All values available across variants (e.g. [\\\"Red\\\", \\\"Blue\\\", \\\"Green\\\"]).\n \"\"\"\n values: [String!]!\n}\n\n\"\"\"\nA concrete option/value pair selected on a specific variant.\nExample: { name: \"Color\", value: \"Red\" }.\n\"\"\"\ntype SelectedOption {\n \"\"\"Option axis name.\"\"\"\n name: String!\n\n \"\"\"Selected value.\"\"\"\n value: String!\n}\n\n\"\"\"\nA purchasable unit of a product.\nMaps to VTEX SKU. In commerce, this is the concrete item a customer adds to cart —\na specific size/color/voltage combination of a product.\n\"\"\"\ntype Variant implements Node {\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Variant display name (e.g. \\\"Varsity Red — Size 10\\\").\"\"\"\n name: String!\n\n \"\"\"Stock Keeping Unit code. Unique merchant-side identifier.\"\"\"\n sku: String!\n\n \"\"\"\n PDP path segment in the active locale for this SKU, \"{linkId}-{skuId}\".\n \"\"\"\n routeSlug: String!\n\n \"\"\"International Article Number (barcode). Maps to VTEX SKU EAN.\"\"\"\n ean: String\n\n \"\"\"Merchant reference code.\"\"\"\n referenceId: String\n\n \"\"\"Whether this variant is currently available for purchase.\"\"\"\n availableForSale: Boolean!\n\n \"\"\"Current selling price.\"\"\"\n price: Money!\n\n \"\"\"Original list price before promotions. Null when there is no discount.\"\"\"\n compareAtPrice: Money\n\n \"\"\"All option selections that define this variant.\"\"\"\n selectedOptions: [SelectedOption!]!\n\n \"\"\"Variant images. First image is the primary.\"\"\"\n images(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int = 10\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): ImagesConnection!\n\n \"\"\"Variant videos.\"\"\"\n videos: [Video!]!\n\n \"\"\"Quantity currently in stock. Null when inventory tracking is disabled.\"\"\"\n quantityAvailable: Int\n\n \"\"\"Unit of sale (e.g. \\\"un\\\", \\\"kg\\\", \\\"l\\\").\"\"\"\n measurementUnit: String!\n\n \"\"\"\n Multiplier for the measurement unit (e.g. 1.5 means each unit is 1.5 kg).\n \"\"\"\n unitMultiplier: Float!\n\n \"\"\"The parent product.\"\"\"\n product: Product!\n\n \"\"\"Variant-level specifications (e.g. voltage, plug type).\"\"\"\n specifications: [Specification!]!\n\n \"\"\"Add-on customizations available for this variant.\"\"\"\n customizations: [Customization!]!\n\n \"\"\"Sellers that fulfill this variant. Relevant in marketplace scenarios.\"\"\"\n sellers: [Seller!]!\n}\n\n\"\"\"Paginated list of variants.\"\"\"\ntype VariantsConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [VariantEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Total number of variants matching the query.\"\"\"\n totalCount: Int!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype VariantEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Variant!\n}\n\n\"\"\"\nA product is a generic item in the catalog (e.g. \"Nike Zoom Janoski\").\nEach product has one or more variants (SKUs) that are the purchasable units.\nMaps to the VTEX Product entity.\n\"\"\"\ntype Product implements Node\n @key(fields: \"slug\")\n @cacheControl(maxAge: 60, scope: PUBLIC)\n{\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Product title as displayed in the storefront.\"\"\"\n title: String!\n\n \"\"\"URL-friendly handle / slug. Maps to VTEX `LinkId`.\"\"\"\n slug: String!\n\n \"\"\"\n PDP path segment in the active locale, \"{linkId}-{skuId}\" with the representative SKU (in stock first, then lowest\n spot price, then the first SKU). Build product URLs from this, not from `slug`.\n \"\"\"\n routeSlug: String!\n\n \"\"\"Full marketing description.\"\"\"\n description: String\n\n \"\"\"Description rendered as HTML.\"\"\"\n descriptionHtml: HTML\n\n \"\"\"Vendor / manufacturer name. Shortcut for brand.name.\"\"\"\n vendor: String\n\n \"\"\"Whether this product is currently active on the storefront.\"\"\"\n isActive: Boolean!\n\n \"\"\"Whether to show the product even when out of stock.\"\"\"\n showWhenOutOfStock: Boolean!\n\n \"\"\"Date the product was first published.\"\"\"\n publishedAt: DateTime\n\n \"\"\"Release or launch date.\"\"\"\n releaseDate: DateTime\n\n \"\"\"Date the product was created in the system.\"\"\"\n createdAt: DateTime!\n\n \"\"\"Date the product was last updated.\"\"\"\n updatedAt: DateTime!\n\n \"\"\"Merchant reference ID. Maps to VTEX `RefId`.\"\"\"\n referenceId: String\n\n \"\"\"The brand this product belongs to.\"\"\"\n brand: Brand\n\n \"\"\"The primary (most specific) category.\"\"\"\n category: Category\n\n \"\"\"All ancestor categories up to the root department.\"\"\"\n categories: [Category!]!\n\n \"\"\"The price range across all variants.\"\"\"\n priceRange: PriceRange!\n\n \"\"\"The compare-at (original) price range across all variants.\"\"\"\n compareAtPriceRange: PriceRange\n\n \"\"\"Named option axes available on this product (e.g. Color, Size).\"\"\"\n options: [ProductOption!]!\n\n \"\"\"Product-level specifications (e.g. material, care instructions).\"\"\"\n specifications: [Specification!]!\n\n \"\"\"Specification groups with full detail.\"\"\"\n specificationGroups: [SpecificationGroup!]!\n\n \"\"\"All purchasable variants.\"\"\"\n variants(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): VariantsConnection!\n\n \"\"\"Total number of variants.\"\"\"\n totalVariants: Int!\n\n \"\"\"Product images aggregated from all variants.\"\"\"\n images(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int = 10\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): ImagesConnection!\n\n \"\"\"All media (images + videos) for this product.\"\"\"\n media(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int = 12\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): MediaConnection!\n\n \"\"\"SEO metadata.\"\"\"\n seo: SEO\n\n \"\"\"Navigation breadcrumb trail from root category to this product.\"\"\"\n breadcrumbs: [BreadcrumbItem!]!\n\n \"\"\"User-generated review / rating summary.\"\"\"\n rating: Rating\n\n \"\"\"\n Products related to this one (accessories, suggestions). Maps to VTEX SKU Complement.\n \"\"\"\n relatedProducts(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int = 10\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): ProductsConnection!\n\n \"\"\"Collections this product belongs to.\"\"\"\n collections: [Collection!]!\n\n \"\"\"\n Sales channels where this product is available. Maps to VTEX Trade Policy.\n \"\"\"\n salesChannels: [SalesChannel!]!\n\n \"\"\"Bundle components if this product is a kit. Maps to VTEX SKU Kit.\"\"\"\n bundleItems: [BundleItem!]!\n}\n\n\"\"\"Paginated list of products.\"\"\"\ntype ProductsConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [ProductEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Total number of products matching the query.\"\"\"\n totalCount: Int!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype ProductEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Product!\n}\n\n\"\"\"Paginated list of images.\"\"\"\ntype ImagesConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [ImageEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype ImageEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Image!\n}\n\n\"\"\"Paginated list of media.\"\"\"\ntype MediaConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [MediaEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype MediaEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Media!\n}\n\n\"\"\"\nAggregate review/rating summary for a product.\nMaps to VTEX Product Review Rate.\n\"\"\"\ntype Rating {\n \"\"\"Average score (e.g. 4.3).\"\"\"\n average: Float!\n\n \"\"\"Total number of reviews.\"\"\"\n count: Int!\n}\n\n\"\"\"\nA curated group of products. Maps to VTEX Collection (Beta) entity.\nCollections can be used for seasonal promotions, editorial picks, etc.\n\"\"\"\ntype Collection implements Node {\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Collection title.\"\"\"\n title: String!\n\n \"\"\"URL-friendly slug.\"\"\"\n slug: String!\n\n \"\"\"Editorial description.\"\"\"\n description: String\n\n \"\"\"Whether the collection is currently active.\"\"\"\n isActive: Boolean!\n\n \"\"\"Hero / banner image.\"\"\"\n image: Image\n\n \"\"\"SEO metadata.\"\"\"\n seo: SEO\n\n \"\"\"Paginated products in this collection.\"\"\"\n products(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n\n \"\"\"Sorting criteria.\"\"\"\n orderBy: ProductSortingInput\n\n \"\"\"Filter criteria.\"\"\"\n filter: ProductsFilterInput\n ): ProductsConnection!\n}\n\n\"\"\"Paginated list of collections.\"\"\"\ntype CollectionsConnection {\n \"\"\"A list of edges.\"\"\"\n edges: [CollectionEdge!]!\n\n \"\"\"Information to aid in pagination.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Total number of collections matching the query.\"\"\"\n totalCount: Int!\n}\n\n\"\"\"An edge in a connection.\"\"\"\ntype CollectionEdge {\n \"\"\"A cursor for pagination.\"\"\"\n cursor: Cursor!\n\n \"\"\"The item at the end of the edge.\"\"\"\n node: Collection!\n}\n\n\"\"\"\nA seller / merchant that fulfills orders for one or more variants.\nMaps to VTEX Seller entity.\n\"\"\"\ntype Seller implements Node {\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Seller display name.\"\"\"\n name: String!\n\n \"\"\"Whether this is the default (1P) seller.\"\"\"\n isDefault: Boolean!\n\n \"\"\"Seller logo.\"\"\"\n logo: Image\n\n \"\"\"\n The seller's commercial offer for this variant (price, availability, installments).\n \"\"\"\n offer: Offer\n}\n\n\"\"\"\nA seller's commercial offer for a specific variant — includes pricing,\navailability, and available payment/installment options.\n\"\"\"\ntype Offer {\n \"\"\"Current selling price.\"\"\"\n price: Money!\n\n \"\"\"Original list price before promotions.\"\"\"\n listPrice: Money!\n\n \"\"\"\n Best cash/immediate-payment price (e.g. Pix discount). Equals price when there is no spot discount.\n \"\"\"\n spotPrice: Money!\n\n \"\"\"Price before any cart-level or coupon discounts.\"\"\"\n priceWithoutDiscount: Money!\n\n \"\"\"Available quantity from this seller.\"\"\"\n availableQuantity: Int!\n\n \"\"\"Tax amount.\"\"\"\n tax: Money!\n\n \"\"\"\n Tax as a percentage of the price (e.g. 0.18 for 18%). Null when not applicable.\n \"\"\"\n taxPercentage: Float\n\n \"\"\"Loyalty / reward points value earned on this purchase.\"\"\"\n rewardValue: Float!\n\n \"\"\"\n ISO 8601 timestamp after which this price is no longer guaranteed. Null when open-ended.\n \"\"\"\n priceValidUntil: DateTime\n\n \"\"\"SKU IDs of free gifts included with this offer.\"\"\"\n giftSkuIds: [String!]!\n\n \"\"\"\n Named discount labels applied to this offer (e.g. \\\"10% off\\\", \\\"Black Friday\\\").\n \"\"\"\n discountHighlights: [String!]!\n\n \"\"\"Available installment plans grouped by payment method.\"\"\"\n installmentPlans: [InstallmentPlan!]!\n\n \"\"\"\n Active promotions applicable to this offer (e.g. bundle discounts, buy-X-get-Y).\n \"\"\"\n promotions: [Promotion!]!\n}\n\n\"\"\"\nA group of installment options for a single payment method (e.g. \"Visa\", \"Pix\").\n\"\"\"\ntype InstallmentPlan {\n \"\"\"Payment method name (e.g. \\\"Visa\\\", \\\"Mastercard\\\", \\\"Pix\\\").\"\"\"\n paymentMethod: String!\n\n \"\"\"Payment method category.\"\"\"\n paymentType: PaymentType!\n\n \"\"\"\n Available installment tiers, sorted by number of installments ascending.\n \"\"\"\n installments: [Installment!]!\n}\n\n\"\"\"\nA single installment tier within a payment method.\nExample: 6 × R$ 61.65 at 0% interest.\n\"\"\"\ntype Installment {\n \"\"\"Number of installments (1 = single payment).\"\"\"\n count: Int!\n\n \"\"\"Amount per installment.\"\"\"\n value: Money!\n\n \"\"\"Total amount including any interest.\"\"\"\n totalAmount: Money!\n\n \"\"\"Annual interest rate as a percentage (0 = interest-free).\"\"\"\n interestRate: Float!\n\n \"\"\"Whether this tier is interest-free.\"\"\"\n interestFree: Boolean!\n}\n\n\"\"\"Broad category of a payment method.\"\"\"\nenum PaymentType {\n \"\"\"Credit card (Visa, Mastercard, Amex, etc.).\"\"\"\n CREDIT_CARD\n\n \"\"\"Debit card.\"\"\"\n DEBIT_CARD\n\n \"\"\"Instant / real-time payment (Pix, etc.).\"\"\"\n INSTANT\n\n \"\"\"Bank slip / boleto.\"\"\"\n BANK_SLIP\n\n \"\"\"Digital wallet or store-specific payment.\"\"\"\n DIGITAL_WALLET\n\n \"\"\"Other / unknown payment type.\"\"\"\n OTHER\n}\n\n\"\"\"\nAn active promotion or incentive applicable to an offer.\nExamples: \"Buy 3 for R$14.99\", \"9% Pix discount\".\n\"\"\"\ntype Promotion {\n \"\"\"Human-readable promotion name / headline.\"\"\"\n name: String!\n\n \"\"\"Conditions that must be met for the promotion to apply.\"\"\"\n conditions: PromotionConditions!\n\n \"\"\"Effects applied when conditions are met.\"\"\"\n effects: PromotionEffects!\n}\n\n\"\"\"Conditions required for a promotion to activate.\"\"\"\ntype PromotionConditions {\n \"\"\"Minimum quantity of items required (0 = no minimum).\"\"\"\n minimumQuantity: Int!\n\n \"\"\"\n Additional condition parameters (e.g. required payment method, collection membership).\n \"\"\"\n parameters: [PromotionParameter!]!\n}\n\n\"\"\"Effects granted when a promotion's conditions are satisfied.\"\"\"\ntype PromotionEffects {\n \"\"\"Effect parameters describing the discount or benefit.\"\"\"\n parameters: [PromotionParameter!]!\n}\n\n\"\"\"\nA key-value parameter describing a promotion condition or effect.\nCommon keys: \\\"PercentualDiscount\\\", \\\"MaximumUnitPriceDiscount\\\",\n\\\"PaymentMethodId\\\", \\\"CollectionsIdsList\\\".\n\"\"\"\ntype PromotionParameter {\n \"\"\"Parameter key.\"\"\"\n name: String!\n\n \"\"\"Parameter value.\"\"\"\n value: String!\n}\n\n\"\"\"\nA trade policy / sales channel through which products are sold.\nMaps to VTEX Trade Policy / Sales Channel.\n\"\"\"\ntype SalesChannel implements Node\n @cacheControl(maxAge: 3600, scope: PUBLIC)\n{\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Channel name.\"\"\"\n name: String!\n\n \"\"\"ISO 4217 default currency for this channel.\"\"\"\n currencyCode: CurrencyCode!\n\n \"\"\"BCP 47 locale for this channel.\"\"\"\n locale: Locale\n\n \"\"\"Whether the channel is active.\"\"\"\n isActive: Boolean!\n}\n\n\"\"\"\nAn optional, free-form customization that can be attached to a variant.\nMaps to VTEX Attachment. Examples: gift wrapping, engraving, custom message.\n\"\"\"\ntype Customization implements Node {\n \"\"\"Globally unique identifier.\"\"\"\n id: ID!\n\n \"\"\"Display name (e.g. \\\"Gift Message\\\").\"\"\"\n name: String!\n\n \"\"\"Whether this customization is required.\"\"\"\n isRequired: Boolean!\n\n \"\"\"Allowed keys and their constraints.\"\"\"\n fields: [CustomizationField!]!\n}\n\n\"\"\"A single input field within a customization.\"\"\"\ntype CustomizationField {\n \"\"\"Field key (e.g. \\\"message\\\").\"\"\"\n key: String!\n\n \"\"\"Maximum allowed characters.\"\"\"\n maxLength: Int\n\n \"\"\"Regex or enum-based validation domain.\"\"\"\n allowedValues: [String!]\n}\n\n\"\"\"\nA component of a product bundle / kit.\nMaps to VTEX SKU Kit.\n\"\"\"\ntype BundleItem {\n \"\"\"The variant included in the bundle.\"\"\"\n variant: Variant!\n\n \"\"\"Quantity of this variant in the bundle.\"\"\"\n quantity: Int!\n}\n\n\"\"\"Filter criteria for product queries.\"\"\"\ninput ProductsFilterInput {\n \"\"\"Full-text search term.\"\"\"\n searchTerm: String\n\n \"\"\"Include only products in these category IDs.\"\"\"\n categoryIds: [ID!]\n\n \"\"\"Include only products from these brand IDs.\"\"\"\n brandIds: [ID!]\n\n \"\"\"Minimum price filter.\"\"\"\n priceMin: Decimal\n\n \"\"\"Maximum price filter.\"\"\"\n priceMax: Decimal\n\n \"\"\"Include only available (in-stock) products.\"\"\"\n availableOnly: Boolean\n\n \"\"\"Filter by product type / taxonomy label.\"\"\"\n productType: String\n\n \"\"\"\n Filter by specification values, e.g. {name: \\\"Color\\\", values: [\\\"Red\\\", \\\"Blue\\\"]}.\n \"\"\"\n specifications: [SpecificationFilterInput!]\n\n \"\"\"Filter by sales channel ID.\"\"\"\n salesChannelId: ID\n\n \"\"\"Filter by collection ID.\"\"\"\n collectionId: ID\n\n \"\"\"Include only products published after this date.\"\"\"\n publishedAfter: DateTime\n\n \"\"\"Include only products published before this date.\"\"\"\n publishedBefore: DateTime\n\n \"\"\"Filter by tags / keywords.\"\"\"\n tags: [String!]\n}\n\n\"\"\"Allows filtering products by specification name and values.\"\"\"\ninput SpecificationFilterInput {\n \"\"\"Specification name (e.g. \\\"Color\\\").\"\"\"\n name: String!\n\n \"\"\"Accepted values (OR semantics within, AND across different specs).\"\"\"\n values: [String!]!\n}\n\n\"\"\"Sorting input for product queries.\"\"\"\ninput ProductSortingInput {\n \"\"\"Field to sort on.\"\"\"\n field: ProductSortField!\n\n \"\"\"Sort direction.\"\"\"\n direction: SortDirection!\n}\n\n\"\"\"Fields available for sorting products.\"\"\"\nenum ProductSortField {\n \"\"\"Alphabetical by title.\"\"\"\n TITLE\n\n \"\"\"By price (uses min variant price).\"\"\"\n PRICE\n\n \"\"\"By creation date.\"\"\"\n CREATED_AT\n\n \"\"\"By last update date.\"\"\"\n UPDATED_AT\n\n \"\"\"By release date.\"\"\"\n RELEASE_DATE\n\n \"\"\"By relevance / scoring.\"\"\"\n RELEVANCE\n\n \"\"\"By number of sales.\"\"\"\n BEST_SELLING\n}\n\n\"\"\"Filter criteria for collection queries.\"\"\"\ninput CollectionsFilterInput {\n \"\"\"Full-text search term.\"\"\"\n searchTerm: String\n\n \"\"\"Include only active collections.\"\"\"\n activeOnly: Boolean\n}\n\n\"\"\"Sorting input for collection queries.\"\"\"\ninput CollectionSortingInput {\n \"\"\"Field to sort on.\"\"\"\n field: CollectionSortField!\n\n \"\"\"Sort direction.\"\"\"\n direction: SortDirection!\n}\n\n\"\"\"Fields available for sorting collections.\"\"\"\nenum CollectionSortField {\n \"\"\"Alphabetical by title.\"\"\"\n TITLE\n\n \"\"\"By creation date.\"\"\"\n CREATED_AT\n\n \"\"\"By last update date.\"\"\"\n UPDATED_AT\n}\n\n\"\"\"Sorting input for brand queries.\"\"\"\ninput BrandSortingInput {\n \"\"\"Field to sort on.\"\"\"\n field: BrandSortField!\n\n \"\"\"Sort direction.\"\"\"\n direction: SortDirection!\n}\n\n\"\"\"Fields available for sorting brands.\"\"\"\nenum BrandSortField {\n \"\"\"Alphabetical by name.\"\"\"\n NAME\n\n \"\"\"By creation date.\"\"\"\n CREATED_AT\n}\n\n\"\"\"Sorting input for category queries.\"\"\"\ninput CategorySortingInput {\n \"\"\"Field to sort on.\"\"\"\n field: CategorySortField!\n\n \"\"\"Sort direction.\"\"\"\n direction: SortDirection!\n}\n\n\"\"\"Fields available for sorting categories.\"\"\"\nenum CategorySortField {\n \"\"\"Alphabetical by name.\"\"\"\n NAME\n\n \"\"\"By depth level in the tree.\"\"\"\n LEVEL\n}\n\n\"\"\"\nThe result of a full-text product search, including facets for building\nfilter UI.\n\"\"\"\ntype ProductSearchResult {\n \"\"\"Paginated product results.\"\"\"\n products(\n \"\"\"Returns the first N elements.\"\"\"\n first: Int\n\n \"\"\"Returns elements after this cursor.\"\"\"\n after: Cursor\n\n \"\"\"Returns the last N elements.\"\"\"\n last: Int\n\n \"\"\"Returns elements before this cursor.\"\"\"\n before: Cursor\n ): ProductsConnection!\n\n \"\"\"Available facets/filters derived from the result set.\"\"\"\n facets: [SearchFacet!]!\n\n \"\"\"Corrected or suggested query if the original had a typo.\"\"\"\n suggestedQuery: String\n\n \"\"\"Spelling correction details for the query.\"\"\"\n correction: SearchCorrection\n}\n\n\"\"\"Spelling correction information returned by the search engine.\"\"\"\ntype SearchCorrection {\n \"\"\"Whether the original query was corrected.\"\"\"\n corrected: Boolean!\n\n \"\"\"Whether the original query was misspelled.\"\"\"\n misspelled: Boolean!\n\n \"\"\"The corrected query text.\"\"\"\n text: String\n\n \"\"\"The corrected text with `<em>` tags highlighting the changed parts.\"\"\"\n highlighted: String\n}\n\n\"\"\"A facet returned from search, representing a filterable dimension.\"\"\"\ntype SearchFacet {\n \"\"\"Facet name (e.g. \\\"Brand\\\", \\\"Color\\\", \\\"Price Range\\\").\"\"\"\n name: String!\n\n \"\"\"\n Facet key used in filter parameters (e.g. \\\"brand\\\", \\\"category-1\\\", \\\"price\\\").\n \"\"\"\n key: String!\n\n \"\"\"Type discriminator for rendering the correct UI control.\"\"\"\n type: SearchFacetType!\n\n \"\"\"Whether this facet should be hidden from the UI.\"\"\"\n hidden: Boolean!\n\n \"\"\"Total number of values in this facet.\"\"\"\n quantity: Int!\n\n \"\"\"Individual values within this facet.\"\"\"\n values: [SearchFacetValue!]!\n}\n\n\"\"\"Discriminates the facet UI control type.\"\"\"\nenum SearchFacetType {\n \"\"\"A list of discrete values (brand, color, size).\"\"\"\n LIST\n\n \"\"\"A numeric range (price).\"\"\"\n RANGE\n}\n\n\"\"\"A single value within a search facet.\"\"\"\ntype SearchFacetValue {\n \"\"\"Display label.\"\"\"\n label: String!\n\n \"\"\"Value to send back in the filter.\"\"\"\n value: String!\n\n \"\"\"Number of products matching this facet value.\"\"\"\n count: Int!\n\n \"\"\"Whether the user has currently selected this value.\"\"\"\n isSelected: Boolean!\n\n \"\"\"For RANGE facets: the lower bound.\"\"\"\n rangeFrom: Float\n\n \"\"\"For RANGE facets: the upper bound.\"\"\"\n rangeTo: Float\n\n \"\"\"Navigable href for this facet selection.\"\"\"\n href: String\n}\n\n\"\"\"Recommendation strategy hint.\"\"\"\nenum RecommendationIntent {\n \"\"\"Products related by purchase history.\"\"\"\n RELATED\n\n \"\"\"Products frequently bought together.\"\"\"\n COMPLEMENTARY\n\n \"\"\"Similar products (same category / specs).\"\"\"\n SIMILAR\n}\n\n\"\"\"An autocomplete suggestion returned for a partial search query.\"\"\"\ntype AutocompleteSuggestion {\n \"\"\"The suggested search term.\"\"\"\n term: String!\n\n \"\"\"Estimated number of results for this term.\"\"\"\n count: Int!\n\n \"\"\"\n Suggested facet filters associated with this term (e.g. department, category).\n \"\"\"\n attributes: [AutocompleteAttribute!]!\n}\n\n\"\"\"A facet attribute suggested alongside an autocomplete term.\"\"\"\ntype AutocompleteAttribute {\n \"\"\"Attribute key (e.g. \\\"category-1\\\").\"\"\"\n key: String!\n\n \"\"\"Attribute value (e.g. \\\"alimentos-e-bebidas\\\").\"\"\"\n value: String!\n\n \"\"\"Human-readable label for the key (e.g. \\\"Departamento\\\").\"\"\"\n labelKey: String!\n\n \"\"\"Human-readable label for the value (e.g. \\\"Alimentos e bebidas\\\").\"\"\"\n labelValue: String!\n}\n\n\"\"\"A trending search term.\"\"\"\ntype TrendingSearch {\n \"\"\"The search term.\"\"\"\n term: String!\n\n \"\"\"Number of searches in the recent period.\"\"\"\n count: Int!\n}\n\n\"\"\"Identifies a catalog route. Set exactly one member.\"\"\"\ninput CatalogRouteInput {\n \"\"\"\n Relay node ID of the entity behind the route. Only Product IDs are accepted.\n \"\"\"\n id: ID\n\n \"\"\"\n Logical store route, without the binding's origin or base path\n (e.g. \"/apparel/t-shirts\", \"/shirt-51/p\").\n \"\"\"\n path: String\n}\n\n\"\"\"Kind of entity a catalog route resolves to.\"\"\"\nenum CatalogRouteKind {\n \"\"\"A product detail page.\"\"\"\n PRODUCT\n\n \"\"\"A category or subcategory page.\"\"\"\n CATEGORY\n\n \"\"\"A brand page.\"\"\"\n BRAND\n\n \"\"\"A collection page.\"\"\"\n COLLECTION\n}\n\n\"\"\"\nA catalog route and its confirmed equivalents in every locale the catalog registers.\n\"\"\"\ntype CatalogRoute\n @cacheControl(maxAge: 300, scope: PUBLIC)\n{\n \"\"\"\n Relay node ID of the entity. Stable across locales; the route's identity.\n \"\"\"\n id: ID!\n\n \"\"\"Kind of entity behind the route.\"\"\"\n kind: CatalogRouteKind!\n\n \"\"\"\n One entry per locale the catalog confirms for every segment of the route. Editorial fallback never adds an\n entry. May include locales the store does not publish; intersect with the store's bindings.\n \"\"\"\n variants: [CatalogRouteVariant!]!\n}\n\n\"\"\"A route's equivalent in one locale.\"\"\"\ntype CatalogRouteVariant {\n \"\"\"BCP 47 tag, canonicalized.\"\"\"\n locale: String!\n\n \"\"\"\n Route segment(s) in that locale, lowercased, accents kept. Not percent-encoded.\n \"\"\"\n slug: String!\n\n \"\"\"\n Relative logical path (\"/{slug}\" or \"/{slug}/p\"). No origin, no base path, not percent-encoded.\n \"\"\"\n path: String!\n}\n\n\"\"\"\nA shopping cart representing items a customer intends to purchase.\nMaps to the VTEX orderForm entity.\n\"\"\"\ntype Cart implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Globally unique identifier (Relay Node ID).\"\"\"\n id: ID!\n\n \"\"\"Line items currently in the cart.\"\"\"\n items: [CartItem!]!\n\n \"\"\"Aggregated totals (items, shipping, discounts, etc.).\"\"\"\n totalizers: [CartTotalizer!]!\n\n \"\"\"Total cart value.\"\"\"\n total: Money!\n\n \"\"\"\n Server messages from cart processing (e.g. promotions applied, stock warnings).\n \"\"\"\n messages: [CartMessage!]!\n\n \"\"\"\n The coupon codes currently applied to the cart. Empty when none are applied.\n \"\"\"\n appliedCoupons: [AppliedCoupon!]!\n\n \"\"\"\n Cart-level discounts as labeled lines: one per promotion or coupon, plus a\n residual for whatever they do not account for. Always a list (possibly empty),\n never null.\n \n The itemized companion to the aggregated \"Discounts\" totalizer, which stays on\n `totalizers` unchanged: these entries sum to it, so a summary built on them\n cannot under-report. Distinct from the per-line `CartItem.discounts`, which is\n featured-only and itemized per price tag rather than per promotion.\n \"\"\"\n discounts: [CartDiscount!]!\n\n \"\"\"\n Aggregate item-level catalog markdown: the sum of `(listPrice - price) * quantity`\n across non-gift lines, clamped at zero (in cents from VTEX, exposed as Money). This\n is the reduction from each line's list price down to the price the \"Items\" subtotal\n is built on — the catalog \"de/por\" savings baked into the price before any\n cart-level promotion runs.\n \n Distinct money from the cart-level promotions and coupons on the \"Discounts\"\n totalizer and `discounts` (those are `price - sellingPrice`, the reduction below the\n subtotal): item markdown sits *above* the subtotal, so it is never part of\n `totalizers` and never summed into `total`. It is an informational savings figure and\n is zero when no line is marked down. Gift lines are excluded — a gift's value is\n already a promotion entry in `discounts`.\n \"\"\"\n itemMarkdownTotal: Money!\n\n \"\"\"\n Progress toward the cart's nearest free-shipping promotion. Null when no\n eligible free-shipping promotion is identified for this cart.\n \"\"\"\n freeShippingProgress: FreeShippingProgress\n\n \"\"\"\n Non-null when the cart belongs to a different sales channel than the request's active context. The\n cart is left exactly as it was: nothing here reprices it or removes an item. Converging it is the\n `cartSalesChannelSync` write, and that write makes Checkout reprocess the same orderForm — prices,\n promotions, coupons and logistics are recomputed and items not sellable in the destination channel\n are removed by Checkout, while orderFormId, attachments and addresses survive.\n \"\"\"\n salesChannelMismatch: CartSalesChannelMismatch\n}\n\n\"\"\"\nThe two sales channels that disagree. Both are trade-policy identifiers.\n\"\"\"\ntype CartSalesChannelMismatch {\n \"\"\"The channel the cart is currently on.\"\"\"\n cartSalesChannel: String!\n\n \"\"\"The channel the request's active context resolves to.\"\"\"\n activeSalesChannel: String!\n}\n\n\"\"\"\nProgress toward the cart's nearest free-shipping promotion, derived from the\npromotion teasers on a checkout simulation run with the cart's destination.\nNull until the cart has a destination, and when no eligible free-shipping\npromotion is identified for this cart (none configured, none teaser-emitting,\nempty cart, or unreadable promotion data) — absence is a normal state, never\nan error.\n\"\"\"\ntype FreeShippingProgress {\n \"\"\"\n Minimum qualifying cart total for the promotion. Null when the cart already\n qualifies: the promotion teaser disappears once met, so the threshold is no\n longer reported upstream.\n \"\"\"\n threshold: Money\n\n \"\"\"Amount still to add to qualify. Zero when the cart qualifies.\"\"\"\n remaining: Money!\n\n \"\"\"Whether the cart currently qualifies for free shipping.\"\"\"\n qualified: Boolean!\n}\n\n\"\"\"\nOne labeled cart-level discount line, attributed to the promotion or coupon that\ngranted it. Amounts are summed from the cart's price tags and reconciled against\nthe aggregated \"Discounts\" totalizer.\n\"\"\"\ntype CartDiscount {\n \"\"\"\n The promotion's name, or the coupon code exactly as the shopper entered it.\n Null when nothing upstream names this amount: always for OTHER, and for a\n promotion the platform left unnamed. The consumer supplies the label in that\n case; an internal promotion identifier is never exposed here.\n \"\"\"\n name: String\n\n \"\"\"\n Amount taken off by this line, as a positive monetary value. The minus sign is\n presentational and applied by the UI. Never zero: a promotion that took nothing\n off this cart is omitted rather than reported as an empty line.\n \"\"\"\n value: Money!\n\n \"\"\"\n Where this line came from: an automatic promotion, a coupon, or the residual.\n \"\"\"\n kind: CartDiscountKind!\n}\n\n\"\"\"Origin of a cart-level discount line.\"\"\"\nenum CartDiscountKind {\n \"\"\"An automatically applied promotion.\"\"\"\n PROMOTION\n\n \"\"\"A promotion matched by a coupon code the shopper entered.\"\"\"\n COUPON\n\n \"\"\"\n The part of the aggregate discount that no named promotion or coupon accounts\n for — a promotion the merchant did not feature, a discount the platform never\n itemized, or rounding. At most one entry, always last.\n \"\"\"\n OTHER\n}\n\n\"\"\"\nA coupon code applied to the cart, with whether its promotion conditions matched.\n\"\"\"\ntype AppliedCoupon {\n \"\"\"The coupon code, stored verbatim as the shopper entered it.\"\"\"\n code: String!\n\n \"\"\"\n True when the code is on the cart but its promotion conditions did not match\n (no discount granted).\n \"\"\"\n isUnmatchedCondition: Boolean!\n}\n\n\"\"\"\nA single line item in the cart. Carries a historical snapshot of the variant\nand product at the time it was added: cart-line value objects describe what\nthe customer actually has, not what the catalog says about it today.\n\"\"\"\ntype CartItem {\n \"\"\"\n Cart-scoped line identifier (opaque, stable across mutations for the same line).\n Encoded as Base64(\"CartItem:<orderFormId>:<uniqueId>\"). Not a Relay Node; this id\n exists only to serve as a stable cache key for clients doing optimistic UI.\n \"\"\"\n id: ID!\n\n \"\"\"\n Variant snapshot for this line. Built from the order line itself, not from\n a live catalog read.\n \"\"\"\n variant: CartVariant!\n\n \"\"\"\n Product snapshot for this line. Built from the order line itself, not from\n a live catalog read.\n \"\"\"\n product: CartProduct!\n\n \"\"\"Quantity of this variant in the cart.\"\"\"\n quantity: Int!\n\n \"\"\"Unit price (in cents from VTEX, exposed as Money).\"\"\"\n price: Money!\n\n \"\"\"Original list price before discounts.\"\"\"\n listPrice: Money!\n\n \"\"\"Final selling price after discounts.\"\"\"\n sellingPrice: Money!\n\n \"\"\"Whether this line is a gift item.\"\"\"\n gift: Boolean!\n\n \"\"\"\n Discounts applied to this line, one entry per featured promotion price tag that\n carries a currency amount. Always a list (possibly empty), never null. The\n per-line, itemized companion to the aggregated \"Discounts\" totalizer on Cart —\n not a replacement for it.\n \n Percentage-configured promotions are absent: their price tag carries a rate\n rather than an amount, so there is no monetary value to report per line. Use\n `Cart.discounts` for a breakdown that accounts for every discount, naming what\n it can and reporting the rest as a residual.\n \"\"\"\n discounts: [CartItemDiscount!]!\n\n \"\"\"\n Upstream line identifier (order-form uniqueId). The stable per-line address:\n with customizations, the same variant can appear as several distinct lines,\n so variantId is not unique per line. Stable across cart mutations.\n \"\"\"\n lineId: String!\n\n \"\"\"\n This line's own total (own unit selling price × quantity, rounding-exact),\n excluding composition children.\n \"\"\"\n ownTotal: Money!\n\n \"\"\"\n This line's total including all nested composition children, recursively.\n Equals ownTotal when the line has no compositions.\n \"\"\"\n total: Money!\n\n \"\"\"\n Form-based customizations applied to this line (personalization text,\n selections, uploaded-file URLs). Empty when the line has none.\n \"\"\"\n customizations: [CartItemCustomization!]!\n\n \"\"\"\n Child products added to this line by bundle/assembly choices. Children are\n not top-level cart lines; they ship, and are removed, with their parent.\n \"\"\"\n compositions: [CartItemComposition!]!\n\n \"\"\"\n The cart-wide delivery options this item is eligible for: the same ShippingOption\n entities Shipping.options lists, carrying the shared-pool price, not a per-item\n quote. Excludes pickup options; delivery channel only.\n \"\"\"\n shippingOptions: [ShippingOption!]!\n\n \"\"\"\n This item's own currently selected delivery option, or null when no selection\n is persisted or the persisted selection is a pickup option.\n \"\"\"\n selectedShippingOption: ItemShippingOption\n}\n\n\"\"\"\nOne customization group applied to a cart line, keyed by the upstream\nassembly id. Entries are the shopper's submitted key/value pairs.\n\"\"\"\ntype CartItemCustomization {\n \"\"\"Upstream assembly identifier (e.g. \"Engraving\", \"Optional features\").\"\"\"\n id: String!\n\n \"\"\"\n Submitted values, one entry per input field. File uploads carry the file URL\n as the value.\n \"\"\"\n entries: [CartItemCustomizationEntry!]!\n}\n\n\"\"\"A single submitted customization field.\"\"\"\ntype CartItemCustomizationEntry\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Field name as defined by the assembly (e.g. \"Text\").\"\"\"\n name: String!\n\n \"\"\"Submitted value, stringified. File uploads carry the file URL.\"\"\"\n value: String!\n}\n\n\"\"\"\nA child product nested under a cart line by a bundle/assembly choice. Slim by\ndesign: children are not independently addressable cart lines. May carry its\nown children (nested assemblies).\n\"\"\"\ntype CartItemComposition {\n \"\"\"\n Identifier for this child line, scoped to the cart's order form or to the\n order's group (same opaque encoding as CartItem.id). Cache key only; not\n independently mutable.\n \"\"\"\n id: ID!\n\n \"\"\"Display name as recorded on the order line.\"\"\"\n name: String!\n\n \"\"\"Quantity of this child in the composition.\"\"\"\n quantity: Int!\n\n \"\"\"Unit selling price of the child.\"\"\"\n price: Money!\n\n \"\"\"\n The child's own availability. The parent's variant.availability already\n reflects the worst state across the whole composition.\n \"\"\"\n availability: CartItemAvailability!\n\n \"\"\"\n The assembly group that added this child (upstream parentAssemblyBinding,\n e.g. \"Material Product_Material\").\n \"\"\"\n assemblyId: String!\n\n \"\"\"This child's own total, excluding its nested children.\"\"\"\n ownTotal: Money!\n\n \"\"\"This child's total including its nested children, recursively.\"\"\"\n total: Money!\n\n \"\"\"\n Form-based customizations applied to this child. A composition child is\n itself an order-form line and can carry its own input assemblies. Empty\n when it has none.\n \"\"\"\n customizations: [CartItemCustomization!]!\n\n \"\"\"Nested composition children of this child. Empty when it has none.\"\"\"\n compositions: [CartItemComposition!]!\n}\n\n\"\"\"\nA single discount applied to a cart line, broken out from that line's price tags.\nSourced from items[].priceTags filtered to featured promotions carrying a currency\namount; the aggregate discount total stays on the Cart \"Discounts\" totalizer.\n\"\"\"\ntype CartItemDiscount {\n \"\"\"\n Human-readable label resolved from the matched promotion (e.g. \"Black Friday\").\n Falls back to the price tag identifier when the promotion has no name.\n \"\"\"\n name: String!\n\n \"\"\"\n Amount taken off by this discount, as a positive monetary value. The minus\n sign is presentational and applied by the UI.\n \"\"\"\n value: Money!\n\n \"\"\"\n Always false. A price tag expressing a percentage carries a rate rather than a\n currency amount, so it has no value this type can report and is excluded from\n the list. Retained for compatibility; branching on it gains nothing.\n \"\"\"\n isPercentual: Boolean!\n}\n\n\"\"\"\nStock availability status for a cart line. Mapped from the upstream\norder-form availability strings; UNKNOWN absorbs values we haven't classified.\n\"\"\"\nenum CartItemAvailability {\n \"\"\"In stock and shippable.\"\"\"\n AVAILABLE\n\n \"\"\"Known to be out of stock.\"\"\"\n OUT_OF_STOCK\n\n \"\"\"\n Other known-bad states (cannot deliver, fulfillment unavailable, etc.).\n \"\"\"\n UNAVAILABLE\n\n \"\"\"Upstream returned a state we don't recognize yet.\"\"\"\n UNKNOWN\n}\n\n\"\"\"\nCart-line snapshot of a Variant. Value object: holds the line-level facts\nrecorded at add-to-cart time. Does not implement Node; refetch the live\ncatalog Variant via Query.node(variantId) when needed.\n\"\"\"\ntype CartVariant {\n \"\"\"\n Relay Node ID of the catalog Variant this line refers to. Use it with\n Query.node to refetch the live Variant.\n \"\"\"\n variantId: ID!\n\n \"\"\"Stock-keeping unit code as recorded on the order line.\"\"\"\n sku: String!\n\n \"\"\"\n Display name as recorded on the order line (e.g. \"Classic T-Shirt - Red / M\").\n \"\"\"\n name: String!\n\n \"\"\"\n Single image captured at add-to-cart time. Null when no image URL was recorded.\n \"\"\"\n image: Image\n\n \"\"\"\n International Article Number (barcode). May be empty for items without one.\n \"\"\"\n ean: String\n\n \"\"\"Merchant SKU reference code.\"\"\"\n referenceId: String\n\n \"\"\"Availability classification of this line.\"\"\"\n availability: CartItemAvailability!\n\n \"\"\"Unit of sale (e.g. \"un\", \"kg\").\"\"\"\n measurementUnit: String!\n\n \"\"\"Multiplier for the measurement unit.\"\"\"\n unitMultiplier: Float!\n\n \"\"\"Seller fulfilling this line.\"\"\"\n seller: CartSellerRef!\n\n \"\"\"\n Variations selected on this cart line (e.g. Cor: Vermelho, Tamanho: M). [] when the SKU has\n no variations, never null. Resolved server-side from catalog data (ADR-00012); use this field\n instead of a Query.node(variantId) refetch to read a line's selected options.\n \"\"\"\n selectedOptions: [SelectedOption!]!\n}\n\n\"\"\"\nCart-line snapshot of a Product. Value object: holds the line-level facts\nrecorded at add-to-cart time. Does not implement Node; refetch the live\ncatalog Product via Query.node(productId) when needed.\n\"\"\"\ntype CartProduct {\n \"\"\"\n Relay Node ID of the catalog Product. Use it with Query.node to refetch\n the live Product.\n \"\"\"\n productId: ID!\n\n \"\"\"Product title as recorded on the order line.\"\"\"\n title: String!\n\n \"\"\"Product detail URL as recorded on the order line.\"\"\"\n detailUrl: String!\n\n \"\"\"Categories root-to-leaf as recorded on the order line.\"\"\"\n categories: [CartCategoryRef!]!\n\n \"\"\"\n Brand reference as recorded on the order line. Null when the product has no brand.\n \"\"\"\n brand: CartBrandRef\n}\n\n\"\"\"\nLightweight reference to a Category captured on a cart line. Carries enough\nidentity to deep-link back to the live Category via Query.node.\n\"\"\"\ntype CartCategoryRef {\n \"\"\"Relay Node ID of the catalog Category.\"\"\"\n categoryId: ID!\n\n \"\"\"Category name as recorded on the order line.\"\"\"\n name: String!\n}\n\n\"\"\"Lightweight reference to a Brand captured on a cart line.\"\"\"\ntype CartBrandRef {\n \"\"\"Relay Node ID of the catalog Brand.\"\"\"\n brandId: ID!\n\n \"\"\"Brand name as recorded on the order line.\"\"\"\n name: String!\n}\n\n\"\"\"Lightweight reference to a Seller captured on a cart line.\"\"\"\ntype CartSellerRef {\n \"\"\"Relay Node ID of the catalog Seller.\"\"\"\n sellerId: ID!\n\n \"\"\"Seller display name as recorded on the order line.\"\"\"\n name: String!\n}\n\n\"\"\"\nAn aggregated total section in the cart (e.g. Items, Shipping, Discounts).\n\"\"\"\ntype CartTotalizer {\n \"\"\"Totalizer key (e.g. \"Items\", \"Shipping\", \"Discounts\").\"\"\"\n id: String!\n\n \"\"\"Human-readable label.\"\"\"\n name: String!\n\n \"\"\"Totalizer value.\"\"\"\n value: Money!\n}\n\n\"\"\"\nA server message attached to the cart (promotions applied, warnings, etc.).\n\"\"\"\ntype CartMessage {\n \"\"\"Message code identifier.\"\"\"\n code: String\n\n \"\"\"Human-readable message text.\"\"\"\n text: String!\n\n \"\"\"Message severity (e.g. \"info\", \"warning\", \"error\").\"\"\"\n status: String!\n}\n\n\"\"\"Input for adding an item to a cart.\"\"\"\ninput CartItemAddInput {\n \"\"\"The variant's global Node ID to add.\"\"\"\n variantId: ID!\n\n \"\"\"Quantity to add. Defaults to 1.\"\"\"\n quantity: Int = 1\n\n \"\"\"\n The seller's global Node ID. Defaults to the first-party seller (Seller:1).\n \"\"\"\n seller: ID = \"U2VsbGVyOjE=\"\n}\n\n\"\"\"Input for removing a line from a cart.\"\"\"\ninput CartItemRemoveInput {\n \"\"\"The line to remove: its `CartItem.lineId`.\"\"\"\n lineId: String!\n}\n\n\"\"\"Input for removing multiple lines from a cart in one call.\"\"\"\ninput CartItemsRemoveInput {\n \"\"\"\n The lines to remove: their `CartItem.lineId` values. Duplicates are collapsed.\n \"\"\"\n lineIds: [String!]!\n}\n\n\"\"\"Input for increasing the quantity of a cart line.\"\"\"\ninput CartItemQuantityIncreaseInput {\n \"\"\"The line whose quantity to increase: its `CartItem.lineId`.\"\"\"\n lineId: String!\n\n \"\"\"Amount to add to the current quantity. Defaults to 1.\"\"\"\n quantityToAdd: Int = 1\n}\n\n\"\"\"Input for decreasing the quantity of a cart line.\"\"\"\ninput CartItemQuantityDecreaseInput {\n \"\"\"The line whose quantity to decrease: its `CartItem.lineId`.\"\"\"\n lineId: String!\n\n \"\"\"Amount to subtract from the current quantity. Defaults to 1.\"\"\"\n quantityToRemove: Int = 1\n}\n\n\"\"\"Input for applying a coupon code to the cart.\"\"\"\ninput CartCouponApplyInput {\n \"\"\"The coupon code to apply, sent verbatim.\"\"\"\n code: String!\n}\n\n\"\"\"Input for removing a coupon code from the cart.\"\"\"\ninput CartCouponRemoveInput {\n \"\"\"The coupon code to remove from the applied set.\"\"\"\n code: String!\n}\n\n\"\"\"Error codes for cart item add operations.\"\"\"\nenum CartItemAddErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The quantity must be a positive integer.\"\"\"\n INVALID_QUANTITY\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The provided variant ID is not a valid Variant Node ID.\"\"\"\n INVALID_VARIANT_ID\n\n \"\"\"The provided seller ID is not a valid Seller Node ID.\"\"\"\n INVALID_SELLER_ID\n}\n\n\"\"\"Error codes for cart item remove operations.\"\"\"\nenum CartItemRemoveErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The specified line was not found in the cart.\"\"\"\n ITEM_NOT_FOUND\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The upstream checkout API returned an unusable response.\"\"\"\n UPSTREAM_ERROR\n}\n\n\"\"\"Error codes for cart items batch remove operations.\"\"\"\nenum CartItemsRemoveErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"None of the specified lines were found in the cart.\"\"\"\n ITEM_NOT_FOUND\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The input contained no line IDs.\"\"\"\n NO_LINE_IDS_PROVIDED\n\n \"\"\"The upstream checkout API returned an unusable response.\"\"\"\n UPSTREAM_ERROR\n}\n\n\"\"\"Error codes for cart item quantity increase operations.\"\"\"\nenum CartItemQuantityIncreaseErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The quantity must be a positive integer.\"\"\"\n INVALID_QUANTITY\n\n \"\"\"The specified line was not found in the cart.\"\"\"\n ITEM_NOT_FOUND\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The upstream checkout API returned an unusable response.\"\"\"\n UPSTREAM_ERROR\n}\n\n\"\"\"Error codes for cart item quantity decrease operations.\"\"\"\nenum CartItemQuantityDecreaseErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The quantity must be a positive integer.\"\"\"\n INVALID_QUANTITY\n\n \"\"\"The specified line was not found in the cart.\"\"\"\n ITEM_NOT_FOUND\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The upstream checkout API returned an unusable response.\"\"\"\n UPSTREAM_ERROR\n}\n\n\"\"\"A domain error returned from a cart item add mutation.\"\"\"\ntype CartItemAddError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartItemAddErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a cart item remove mutation.\"\"\"\ntype CartItemRemoveError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartItemRemoveErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a cart items batch remove mutation.\"\"\"\ntype CartItemsRemoveError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartItemsRemoveErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a cart item quantity increase mutation.\"\"\"\ntype CartItemQuantityIncreaseError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartItemQuantityIncreaseErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a cart item quantity decrease mutation.\"\"\"\ntype CartItemQuantityDecreaseError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartItemQuantityDecreaseErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the cartItemAdd mutation.\"\"\"\ntype CartItemAddPayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartItemAddError!]!\n}\n\n\"\"\"Payload returned by the cartItemRemove mutation.\"\"\"\ntype CartItemRemovePayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartItemRemoveError!]!\n}\n\n\"\"\"Payload returned by the cartItemsRemove mutation.\"\"\"\ntype CartItemsRemovePayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartItemsRemoveError!]!\n}\n\n\"\"\"Payload returned by the cartItemQuantityIncrease mutation.\"\"\"\ntype CartItemQuantityIncreasePayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartItemQuantityIncreaseError!]!\n}\n\n\"\"\"Payload returned by the cartItemQuantityDecrease mutation.\"\"\"\ntype CartItemQuantityDecreasePayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartItemQuantityDecreaseError!]!\n}\n\n\"\"\"Error codes for cart coupon apply operations.\"\"\"\nenum CartCouponApplyErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The coupon code is empty, unknown, or not accepted by this cart.\"\"\"\n COUPON_NOT_FOUND\n\n \"\"\"The coupon code has expired.\"\"\"\n COUPON_EXPIRED\n\n \"\"\"\n The code was rejected while another coupon was applied, and the applied coupons were kept. The\n platform does not say whether the code is unknown or cannot be combined with them.\n \"\"\"\n COUPON_NOT_COMBINED\n}\n\n\"\"\"Error codes for cart coupon remove operations.\"\"\"\nenum CartCouponRemoveErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n A surviving coupon was rejected by the cart after the removal (unknown or not accepted).\n \"\"\"\n COUPON_NOT_FOUND\n\n \"\"\"A surviving coupon has expired.\"\"\"\n COUPON_EXPIRED\n}\n\n\"\"\"A domain error returned from a cart coupon apply mutation.\"\"\"\ntype CartCouponApplyError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartCouponApplyErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a cart coupon remove mutation.\"\"\"\ntype CartCouponRemoveError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartCouponRemoveErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the cartCouponApply mutation.\"\"\"\ntype CartCouponApplyPayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartCouponApplyError!]!\n}\n\n\"\"\"Payload returned by the cartCouponRemove mutation.\"\"\"\ntype CartCouponRemovePayload {\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartCouponRemoveError!]!\n}\n\n\"\"\"Error codes for cart validation operations.\"\"\"\nenum CartValidateErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n The upstream checkout API could not be reached or returned an unusable response.\n \"\"\"\n UPSTREAM_ERROR\n}\n\n\"\"\"A domain error returned from a cart validate mutation.\"\"\"\ntype CartValidateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartValidateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the cartValidate mutation.\"\"\"\ntype CartValidatePayload {\n \"\"\"The re-checked cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartValidateError!]!\n}\n\n\"\"\"Error codes for moving a cart onto the active sales channel.\"\"\"\nenum CartSalesChannelSyncErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n The upstream checkout API could not be reached or returned an unusable response.\n \"\"\"\n UPSTREAM_ERROR\n}\n\n\"\"\"A domain error returned from the cartSalesChannelSync mutation.\"\"\"\ntype CartSalesChannelSyncError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CartSalesChannelSyncErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"\nPayload returned by the cartSalesChannelSync mutation.\n\nThe cart comes from the request context and is never created. An empty cart, or a cart already on\nthe active sales channel, does not send `sc`. Otherwise one POST to `items/update` with `sc`\nre-sends every item's index and current quantity, so Checkout reprocesses that order form and drops\nlines the destination cannot sell, and then the locale is reconciled. An upstream failure is\nreported in `userErrors` and does not throw.\n\"\"\"\ntype CartSalesChannelSyncPayload {\n \"\"\"\n The cart after the channel move and locale reconciliation, or null if errors occurred.\n \"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [CartSalesChannelSyncError!]!\n}\n\n\"\"\"The delivery channel for a shipping option.\"\"\"\nenum DeliveryChannel {\n \"\"\"Home delivery.\"\"\"\n DELIVERY\n\n \"\"\"Pickup in store or pickup point.\"\"\"\n PICKUP\n\n \"\"\"Unrecognised delivery channel returned by the fulfillment provider.\"\"\"\n UNKNOWN\n}\n\n\"\"\"The unit of a shipping time estimate.\"\"\"\nenum ShippingEstimateUnit {\n \"\"\"Calendar days (e.g. \"7d\").\"\"\"\n DAYS\n\n \"\"\"Business days, excluding weekends and holidays (e.g. \"5bd\").\"\"\"\n BUSINESS_DAYS\n\n \"\"\"Hours (e.g. \"7h\").\"\"\"\n HOURS\n\n \"\"\"Minutes (e.g. \"119m\").\"\"\"\n MINUTES\n}\n\n\"\"\"\nParsed shipping time estimate (e.g. \"5bd\" → { value: 5, unit: BUSINESS_DAYS }).\n\"\"\"\ntype ShippingEstimate {\n \"\"\"Numeric quantity of the estimate.\"\"\"\n value: Int!\n\n \"\"\"Time unit for the estimate.\"\"\"\n unit: ShippingEstimateUnit!\n}\n\n\"\"\"\nOne cart item's current selection. Its price and estimate are Checkout's real values for\nthis item; option carries the shared name and channel. All values are a live snapshot:\nanother item joining or leaving the same SLA can change them.\n\"\"\"\ntype ItemShippingOption {\n \"\"\"\n Opaque delivery option identifier, encoded to distinguish per-item options\n from cart-wide options so the Apollo cache never conflates them.\n \"\"\"\n id: ID!\n\n \"\"\"The cart-wide shipping option this selection belongs to.\"\"\"\n option: ShippingOption!\n\n \"\"\"Estimated shipping time for this item.\"\"\"\n shippingEstimate: ShippingEstimate!\n\n \"\"\"Shipping cost for this item. Zero when free shipping applies.\"\"\"\n price: Money!\n\n \"\"\"Shipping list price for this item before any promotions.\"\"\"\n listPrice: Money!\n}\n\n\"\"\"\nA shipping option with at least one cart item currently selected on it. Its price and\nshippingEstimateList cover only those items, recomputed on every request.\n\"\"\"\ntype SelectedShippingOption {\n \"\"\"The cart-wide shipping option these items selected.\"\"\"\n option: ShippingOption!\n\n \"\"\"\n Cart items currently selected on this option (not every eligible item).\n \"\"\"\n items: [CartItem!]!\n\n \"\"\"Sum of items[].selectedShippingOption.price.\"\"\"\n price: Money!\n\n \"\"\"Sum of items[].selectedShippingOption.listPrice.\"\"\"\n listPrice: Money!\n\n \"\"\"\n items[].selectedShippingOption.shippingEstimate, one entry per item, unreduced and\n uncompared.\n \"\"\"\n shippingEstimateList: [ShippingEstimate!]!\n}\n\n\"\"\"A shipping option available for a given item and destination.\"\"\"\ntype ShippingOption {\n \"\"\"Opaque delivery option identifier.\"\"\"\n id: ID!\n\n \"\"\"\n Human-readable option name (e.g. \"Standard Delivery\"). Derived from a Checkout simulation, which\n sends no `Accept-Language` and keys its SKU cache without a locale, so this text is NOT localized:\n it may read in the account's default culture on a non-default binding.\n \"\"\"\n name: String!\n\n \"\"\"Delivery channel: home delivery or pickup.\"\"\"\n channel: DeliveryChannel!\n\n \"\"\"\n Estimated shipping time, sampled from one eligible item. Delivery estimates can genuinely vary\n per item under the same option; use shippingEstimateList for every item's own value.\n \"\"\"\n shippingEstimate: ShippingEstimate! @deprecated(reason: \"Sampled from one item and can misrepresent the others. Use shippingEstimateList.\") @deprecationDate(deletionDate: \"2027-03-31\")\n\n \"\"\"\n Every eligible item's own shippingEstimate, one entry per item, unreduced and uncompared.\n \"\"\"\n shippingEstimateList: [ShippingEstimate!]!\n\n \"\"\"\n Shipping cost for every item this option covers. Zero when free shipping applies. A live\n estimate of the shared per-SLA pool, not a guaranteed quote.\n \"\"\"\n price: Money!\n\n \"\"\"Shipping list price before any promotions, summed like price.\"\"\"\n listPrice: Money!\n\n \"\"\"Cart items for which this option is currently available.\"\"\"\n items: [CartItem!]!\n}\n\n\"\"\"Error codes for shipping simulation operations.\"\"\"\nenum ShippingSimulateErrorCode {\n \"\"\"The postal code was not found or rejected by the fulfillment provider.\"\"\"\n INVALID_POSTAL_CODE\n\n \"\"\"The provided variant ID is not a valid Variant Node ID.\"\"\"\n INVALID_VARIANT_ID\n\n \"\"\"The provided seller ID is not a valid Seller Node ID.\"\"\"\n INVALID_SELLER_ID\n\n \"\"\"\n No active cart in context: the cartShippingSimulation query requires an x-order-form-id and never mints a cart.\n \"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"\n The country is not an ISO 3166-1 alpha-3 code (three capital letters). Refused before any upstream call.\n \"\"\"\n INVALID_COUNTRY_CODE\n}\n\n\"\"\"A domain error returned from the shippingSimulation query.\"\"\"\ntype ShippingSimulateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: ShippingSimulateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the shippingSimulation query.\"\"\"\ntype ShippingSimulationPayload {\n \"\"\"\n Available shipping options for the given item and destination.\n Null when userErrors is non-empty.\n \"\"\"\n shippingOptions: [ShippingOption!]\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ShippingSimulateError!]!\n}\n\n\"\"\"Input for simulating shipping options for a single variant on a PDP.\"\"\"\ninput ShippingSimulateInput {\n \"\"\"Postal code of the delivery destination.\"\"\"\n postalCode: String!\n\n \"\"\"ISO 3166-1 alpha-3 country code (e.g. \"BRA\").\"\"\"\n country: String!\n\n \"\"\"The variant's global Node ID to simulate shipping for.\"\"\"\n variantId: ID!\n\n \"\"\"Quantity of the variant. Must be at least 1.\"\"\"\n quantity: Int!\n\n \"\"\"The seller's global Node ID responsible for the variant.\"\"\"\n seller: ID!\n}\n\n\"\"\"\nInput for simulating shipping for the whole active cart. Carries the\ndestination only; items are sourced from the cart in the x-order-form-id\ncontext, so there is no variant, quantity, or seller.\n\"\"\"\ninput CartShippingSimulateInput {\n \"\"\"Postal code of the delivery destination.\"\"\"\n postalCode: String!\n\n \"\"\"ISO 3166-1 alpha-3 country code (e.g. \"BRA\").\"\"\"\n country: String!\n}\n\n\"\"\"Payload returned by the cartShippingSimulation query.\"\"\"\ntype CartShippingSimulationPayload {\n \"\"\"\n Shipping options available for every item in the cart, prices summed across\n items. Empty when the cart is empty or no option is common to all items.\n Null when userErrors is non-empty.\n \"\"\"\n shippingOptions: [ShippingOption!]\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ShippingSimulateError!]!\n}\n\n\"\"\"\nThe shopper's profile, projected from the order form's clientProfileData.\nA sibling of Cart, not nested under it. Individual (B2C) only.\n\"\"\"\ntype ClientProfile implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order form ID.\n \"\"\"\n id: ID!\n\n \"\"\"\n Contact email as recorded on the order form. Null when no profile is set.\n \"\"\"\n email: String\n\n \"\"\"Given name. Null when unset.\"\"\"\n firstName: String\n\n \"\"\"Family name. Null when unset.\"\"\"\n lastName: String\n\n \"\"\"Contact phone in the order form's stored format. Null when unset.\"\"\"\n phone: String\n\n \"\"\"Tax document (CPF for Individual shoppers). Null when unset.\"\"\"\n document: String\n}\n\n\"\"\"\nThe shopper's preference opt-ins, projected from the order form's\nclientPreferencesData. A sibling of Cart, not nested under it.\n\"\"\"\ntype ClientPreferences implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order form ID.\n \"\"\"\n id: ID!\n\n \"\"\"Newsletter opt-in. Null when the shopper has not chosen.\"\"\"\n optinNewsLetter: Boolean\n\n \"\"\"Save-personal-data opt-in. Null when the shopper has not chosen.\"\"\"\n savePersonalData: Boolean\n}\n\n\"\"\"\nPartial profile update. Provide only the fields to write; omitted fields are\nleft unchanged.\n\"\"\"\ninput ClientProfileUpdateInput {\n \"\"\"Contact email to write. Omit to leave unchanged.\"\"\"\n email: String\n\n \"\"\"Given name to write. Omit to leave unchanged.\"\"\"\n firstName: String\n\n \"\"\"Family name to write. Omit to leave unchanged.\"\"\"\n lastName: String\n\n \"\"\"Contact phone to write. Omit to leave unchanged.\"\"\"\n phone: String\n\n \"\"\"Tax document to write. Omit to leave unchanged.\"\"\"\n document: String\n}\n\n\"\"\"\nPartial preferences update. Provide only the flags to write; omitted flags are\nleft unchanged.\n\"\"\"\ninput ClientPreferencesUpdateInput {\n \"\"\"Newsletter opt-in to write. Omit to leave unchanged.\"\"\"\n optinNewsLetter: Boolean\n\n \"\"\"Save-personal-data opt-in to write. Omit to leave unchanged.\"\"\"\n savePersonalData: Boolean\n}\n\n\"\"\"Error codes for client profile update operations.\"\"\"\nenum ClientProfileUpdateErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The input contained no profile fields to write.\"\"\"\n NO_PROFILE_FIELD_PROVIDED\n}\n\n\"\"\"Error codes for client preferences update operations.\"\"\"\nenum ClientPreferencesUpdateErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The input contained no preferences to write.\"\"\"\n NO_PREFERENCE_PROVIDED\n}\n\n\"\"\"A domain error returned from a client profile update mutation.\"\"\"\ntype ClientProfileUpdateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: ClientProfileUpdateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"A domain error returned from a client preferences update mutation.\"\"\"\ntype ClientPreferencesUpdateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: ClientPreferencesUpdateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the clientProfileUpdate mutation.\"\"\"\ntype ClientProfileUpdatePayload {\n \"\"\"The updated profile, or null if a context/validation error occurred.\"\"\"\n clientProfile: ClientProfile\n\n \"\"\"\n The session after the write, reflecting the now-current isIdentified derived from\n clientProfileData.email. Null alongside clientProfile on a context/validation error.\n \"\"\"\n session: Session\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ClientProfileUpdateError!]!\n}\n\n\"\"\"Payload returned by the clientPreferencesUpdate mutation.\"\"\"\ntype ClientPreferencesUpdatePayload {\n \"\"\"\n The updated preferences, or null if a context/validation error occurred.\n \"\"\"\n clientPreferences: ClientPreferences\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ClientPreferencesUpdateError!]!\n}\n\n\"\"\"\nThe delivery address, saved addresses, and shipping options for the cart.\nA read-only sibling of Cart, projected from the same order form fetch.\nNull fields project explicitly (never undefined), allowing partial queries.\n\"\"\"\ntype Shipping implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order form ID.\n \"\"\"\n id: ID!\n\n \"\"\"The delivery address on the cart. Null until the shopper provides one.\"\"\"\n address: Address\n\n \"\"\"The logged-in shopper's saved addresses. Empty for anonymous shoppers.\"\"\"\n savedAddresses: [Address!]!\n\n \"\"\"\n Delivery-channel shipping options any cart item is eligible for at the current address, prices summed across the eligible items.\n \"\"\"\n options: [ShippingOption!]!\n\n \"\"\"\n Delivery-channel options with at least one item currently selected on them, one entry per option. Pickup selections are excluded.\n \"\"\"\n selectedOptions: [SelectedShippingOption!]!\n\n \"\"\"\n The selected option, when one selection applies to the whole cart. Null when items disagree or the persisted selection is a pickup-channel SLA.\n \"\"\"\n selectedOptionId: ID @deprecated(reason: \"Cannot express split shipping. Use Shipping.selectedOptions.\") @deprecationDate(deletionDate: \"2027-03-31\")\n}\n\n\"\"\"A delivery address or saved-address reference.\"\"\"\ntype Address\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Saved-address reference. Null for addresses entered in this session.\"\"\"\n id: ID\n\n \"\"\"Recipient name for this address.\"\"\"\n recipientName: String\n\n \"\"\"\n Postal code. Required when persisting an address via the mutation inputs, but nullable here:\n pickup/search/disposable addresses and legacy saved addresses can come back without one.\n \"\"\"\n postalCode: String\n\n \"\"\"\n ISO 3166-1 alpha-3 country code. Required when persisting an address via the mutation inputs,\n but nullable here for the same reason as postalCode.\n \"\"\"\n country: String\n\n \"\"\"Street name.\"\"\"\n street: String\n\n \"\"\"Street number.\"\"\"\n number: String\n\n \"\"\"Additional address detail (apartment, suite, etc.).\"\"\"\n complement: String\n\n \"\"\"Neighborhood / district.\"\"\"\n neighborhood: String\n\n \"\"\"City name.\"\"\"\n city: String\n\n \"\"\"State / province code.\"\"\"\n state: String\n}\n\n\"\"\"\nThe shopper's session and authentication state for the active order form.\nA read-only sibling of Cart, projected from the same order form fetch. Carries\nno profile data — profile fields live on ClientProfile.\n\"\"\"\ntype Session implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order form ID.\n \"\"\"\n id: ID!\n\n \"\"\"\n Whether the shopper holds an authenticated VTEX session. A guest is `false` — a normal state, never an error.\n \"\"\"\n loggedIn: Boolean!\n\n \"\"\"\n Whether the shopper may edit their identity data. Lets Controls render editable vs. identified-but-masked fields.\n \"\"\"\n canEditData: Boolean!\n\n \"\"\"\n Server-computed: whether a profile is attached to the order form (clientProfileData.email is present and non-empty). Identification is distinct from authentication.\n \"\"\"\n isIdentified: Boolean!\n}\n\n\"\"\"Input for updating the delivery address on the cart.\"\"\"\ninput ShippingAddressUpdateInput {\n \"\"\"\n Saved address reference. Present when the shopper picked (and possibly edited) a saved address.\n \"\"\"\n savedAddressId: ID\n\n \"\"\"\n Recipient name for the delivery address. Omit it for a destination-only write: the cart keeps its current recipient name. A blank string is rejected with MISSING_RECIPIENT_NAME.\n \"\"\"\n recipientName: String\n\n \"\"\"Postal code for the delivery address.\"\"\"\n postalCode: String!\n\n \"\"\"ISO 3166-1 alpha-3 country code (e.g. \"BRA\").\"\"\"\n country: String!\n\n \"\"\"Street name.\"\"\"\n street: String\n\n \"\"\"Street number.\"\"\"\n number: String\n\n \"\"\"Additional address detail (apartment, suite, etc.).\"\"\"\n complement: String\n\n \"\"\"Neighborhood / district.\"\"\"\n neighborhood: String\n\n \"\"\"City name.\"\"\"\n city: String\n\n \"\"\"State / province code.\"\"\"\n state: String\n}\n\n\"\"\"Error codes for shipping address update operations.\"\"\"\nenum ShippingAddressUpdateErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The recipient name is blank.\"\"\"\n MISSING_RECIPIENT_NAME\n\n \"\"\"The provided saved address ID does not match any available addresses.\"\"\"\n INVALID_SAVED_ADDRESS_ID\n\n \"\"\"The postal code is invalid or not found.\"\"\"\n INVALID_POSTAL_CODE\n\n \"\"\"The address is invalid.\"\"\"\n INVALID_ADDRESS\n\n \"\"\"\n The country is not an ISO 3166-1 alpha-3 code (three capital letters). Refused before any upstream call.\n \"\"\"\n INVALID_COUNTRY_CODE\n}\n\n\"\"\"A domain error returned from a shipping address update mutation.\"\"\"\ntype ShippingAddressUpdateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: ShippingAddressUpdateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the shippingAddressUpdate mutation.\"\"\"\ntype ShippingAddressUpdatePayload {\n \"\"\"The updated shipping state, or null if errors occurred.\"\"\"\n shipping: Shipping\n\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ShippingAddressUpdateError!]!\n}\n\n\"\"\"Input for selecting a shipping option for the whole cart.\"\"\"\ninput ShippingOptionSelectInput {\n \"\"\"\n The shipping option ID to select. Must be offered as a delivery option by at least one item in\n the cart; items that don't offer it fall back to the Checkout API's own choice.\n \"\"\"\n optionId: ID!\n}\n\n\"\"\"Error codes for shipping option select operations.\"\"\"\nenum ShippingOptionSelectErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n The option id is not offered as a delivery option by any item in the cart (unknown id or\n pickup). Returned without calling Checkout. An option only some items offer is not an error.\n \"\"\"\n OPTION_UNAVAILABLE\n\n \"\"\"No delivery address is persisted on the cart.\"\"\"\n MISSING_SHIPPING_ADDRESS\n}\n\n\"\"\"A domain error returned from a shipping option select mutation.\"\"\"\ntype ShippingOptionSelectError {\n \"\"\"Machine-readable error code.\"\"\"\n code: ShippingOptionSelectErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the shippingOptionSelect mutation.\"\"\"\ntype ShippingOptionSelectPayload {\n \"\"\"The updated shipping state, or null if errors occurred.\"\"\"\n shipping: Shipping\n\n \"\"\"The updated cart, or null if errors occurred.\"\"\"\n cart: Cart\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [ShippingOptionSelectError!]!\n}\n\n\"\"\"\nThe cart's payment state: available methods with server-driven validator config,\nthe current selection, installment options, and the shopper's saved cards. A\nread-only sibling of Cart, Shipping, and Session, projected from the same order\nform fetch. Method-agnostic: projects whatever payment systems VTEX returns, not\nonly credit card. Never carries a full card number or CVV.\n\"\"\"\ntype Payment implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order form ID.\n \"\"\"\n id: ID!\n\n \"\"\"\n Payment methods available on this cart. Empty when none are configured.\n \"\"\"\n methods: [PaymentMethod!]!\n\n \"\"\"\n The currently selected method's id (PaymentMethod.id), or null when unselected.\n \"\"\"\n selectedMethodId: String\n\n \"\"\"\n Saved-card account reference for the current selection, when a saved card is selected. Null otherwise.\n \"\"\"\n selectedAccountId: String\n\n \"\"\"\n The currently selected installment tier, resolved from the selection against installmentOptions. Null when unselected or unresolved.\n \"\"\"\n selectedInstallment: PaymentInstallment\n\n \"\"\"\n Installment plans available, grouped by method (and BIN, for card methods). Serves both a new card (matched by BIN) and a saved card (matched by its stored method).\n \"\"\"\n installmentOptions: [PaymentInstallmentOption!]!\n\n \"\"\"\n The shopper's saved cards. Empty for anonymous shoppers or when none are saved.\n \"\"\"\n savedCards: [SavedCard!]!\n\n \"\"\"\n The BIN persisted on the current selection, or null when the selection carries\n none (a non-card method, or a saved card selected without one). Echoes back what\n paymentMethodSelect wrote, which is what a client needs in order to know whether\n its own locally-detected BIN has actually reached the order form: installment\n options are matched against THIS value server-side, never against a client draft.\n Not sensitive — a BIN identifies an issuer range, never a card.\n \"\"\"\n selectedBin: String\n\n \"\"\"\n The store's country as an ISO 3166-1 alpha-3 code (e.g. \"BRA\"), read from the order form's\n store preferences. Null when the order form carries none or the value is not three capital\n letters. It is not limited to the countries in `countries`: a store in another country gets its\n own code and no table entry, and the API never substitutes another country's entry.\n \"\"\"\n storeCountryCode: String\n\n \"\"\"\n The countries the payment data supports and, per country, the cardholder document types it\n accepts. A fixed table, identical in every response and independent of the cart. Order: USA,\n BRA, CAN, MEX. Select the entry whose countryCode equals storeCountryCode.\n \"\"\"\n countries: [PaymentCountry!]!\n}\n\n\"\"\"\nA country the payment data supports, with the cardholder document types it accepts. A\nnon-normalized value object read directly off Payment.countries.\n\"\"\"\ntype PaymentCountry {\n \"\"\"ISO 3166-1 alpha-3 country code (e.g. \"BRA\").\"\"\"\n countryCode: String!\n\n \"\"\"\n The document types a cardholder of this country can give. Masks and validation rules are not\n carried: the client keys them on the document type.\n \"\"\"\n documents: [PaymentCountryDocument!]!\n}\n\n\"\"\"\nOne cardholder document type of a PaymentCountry, with whether it is shown and whether it is\nrequired. Values follow @vtex/country-forms 1.2.0 for an individual person.\n\"\"\"\ntype PaymentCountryDocument {\n \"\"\"Document type id, lowercase (e.g. \"cpf\", \"ssn\", \"sin\", \"rfc\").\"\"\"\n type: String!\n\n \"\"\"Whether the document field should be shown for this country.\"\"\"\n displayable: Boolean!\n\n \"\"\"Whether the document is required for this country.\"\"\"\n required: Boolean!\n}\n\n\"\"\"\nA payment method available on the cart, with the server-driven validator config\nthat determines which card-form fields render, their masks, and whether they\nare required. A non-normalized value object (ADR-0012); read directly off\nPayment.methods.\n\"\"\"\ntype PaymentMethod {\n \"\"\"Payment-system identifier (VTEX paymentSystem stringId).\"\"\"\n id: String!\n\n \"\"\"\n Display name (e.g. \"Visa\", \"Mastercard\", \"Cash\"). Doubles as the network name for card methods.\n \"\"\"\n name: String!\n\n \"\"\"\n Payment-system group (e.g. \"creditCardPaymentGroup\", \"debitCardPaymentGroup\"). Store-configured; kept as a string, not an enum.\n \"\"\"\n groupName: String!\n\n \"\"\"\n Whether VTEX flags this payment system as requiring cardholder authentication\n (for example 3-D Secure). Projected verbatim from the order form's payment-system\n configuration: it describes how the store configured the system, not a guarantee\n about any particular transaction. Null when the order form carries no value for\n this system — which is not the same as false. Detection only: this API implements\n no authentication challenge.\n \"\"\"\n requiresAuthentication: Boolean\n\n \"\"\"\n Validator configuration driving field presence, masks, and requiredness.\n \"\"\"\n validator: PaymentMethodValidator!\n}\n\n\"\"\"\nPer-method validator configuration. Server-driven so no component hardcodes\nfield presence, masks, or requiredness.\n\"\"\"\ntype PaymentMethodValidator {\n \"\"\"\n Mask pattern for the card number input. Null when the method carries no mask.\n \"\"\"\n numberMask: String\n\n \"\"\"\n Regex for real-time network detection against the typed BIN. Null when unset.\n \"\"\"\n numberRegex: String\n\n \"\"\"\n Mask pattern for the security-code (CVV) input. Null when unused or unmasked.\n \"\"\"\n cvvMask: String\n\n \"\"\"Regex validating the security-code (CVV) input. Null when unused.\"\"\"\n cvvRegex: String\n\n \"\"\"Whether this method requires the security code (CVV).\"\"\"\n cvvRequired: Boolean!\n\n \"\"\"Whether this method requires an expiration date.\"\"\"\n expirationRequired: Boolean!\n\n \"\"\"Whether this method requires the cardholder name.\"\"\"\n holderNameRequired: Boolean!\n\n \"\"\"Whether this method requires a billing address.\"\"\"\n billingAddressRequired: Boolean!\n\n \"\"\"\n Whether a cardholder document field is displayable for this method. Distinct from documentRequired.\n \"\"\"\n documentDisplayable: Boolean! @deprecated(reason: \"The document type, mask and country come from Payment.countries. Storefront clients still read this flag as the merchant's per-method gate until a per-method replacement exists.\") @deprecationDate(deletionDate: \"2027-09-30\")\n\n \"\"\"\n Whether the displayed cardholder document is required. Distinct from documentDisplayable — a document can be shown without being mandatory.\n \"\"\"\n documentRequired: Boolean! @deprecated(reason: \"The document type, mask and country come from Payment.countries. Storefront clients still read this flag as the merchant's per-method gate until a per-method replacement exists.\") @deprecationDate(deletionDate: \"2027-09-30\")\n}\n\n\"\"\"\nOne installment tier: a specific installment count at a given per-installment\nvalue and total. Keyed by count within its parent PaymentInstallmentOption — a\nnon-normalized value object (ADR-0012).\n\"\"\"\ntype PaymentInstallment {\n \"\"\"Number of installments (1 = single payment, no interest).\"\"\"\n count: Int!\n\n \"\"\"Amount per installment.\"\"\"\n value: Money!\n\n \"\"\"Total amount across all installments, including any interest.\"\"\"\n total: Money!\n\n \"\"\"\n Interest rate in hundredths of a percent, exactly as the VTEX order form carries it (100 = 1%; 0 = interest-free).\n \"\"\"\n interestRate: Float!\n}\n\n\"\"\"\nInstallment tiers available for one payment method (and, for card methods, one\nBIN range). A non-normalized value object (ADR-0012): read straight off\nPayment.installmentOptions.\n\"\"\"\ntype PaymentInstallmentOption\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"The payment method this option applies to (PaymentMethod.id).\"\"\"\n methodId: String!\n\n \"\"\"\n The BIN range this option applies to, when method-and-BIN-specific. Null when the option applies by method alone.\n \"\"\"\n bin: String\n\n \"\"\"Base order value this option was quoted against.\"\"\"\n baseValue: Money!\n\n \"\"\"\n Installment tiers, with a guaranteed `count: 1` entry prepended when VTEX\n omits one. A tier that upstream already supplies is passed through untouched\n (including its position and interest rate).\n \"\"\"\n installments: [PaymentInstallment!]!\n}\n\n\"\"\"\nOne of the shopper's saved cards. Never carries a full card number or CVV. A\nnormalized, id-bearing value object (ADR-0012): id is a stable opaque cache\nkey, not a Relay Node.\n\"\"\"\ntype SavedCard\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Opaque, stable cache key. Not a Relay Node ID; SavedCard is not registered in the cross-module Node registry.\n \"\"\"\n id: ID!\n\n \"\"\"\n The saved-card account reference. Pass this back on selection; never a full card number.\n \"\"\"\n accountId: String!\n\n \"\"\"\n The saved card's payment-system id (PaymentMethod.id), for cross-referencing validator config and installment options.\n \"\"\"\n methodId: String!\n\n \"\"\"Card network / issuer display name (e.g. \"Visa\").\"\"\"\n network: String!\n\n \"\"\"Last four digits of the card number.\"\"\"\n lastFourDigits: String!\n\n \"\"\"BIN of the saved card, for installment-option lookup by network.\"\"\"\n bin: String!\n\n \"\"\"Shopper-assigned custom label, when set.\"\"\"\n label: String\n\n \"\"\"\n Whether the card is expired. Expired cards are still returned; non-selectable is a UI concern downstream.\n \"\"\"\n expired: Boolean!\n\n \"\"\"\n Whether completing payment with this card requires the shopper to enter the CVV.\n \"\"\"\n cvvRequired: Boolean!\n}\n\n\"\"\"\nInput for selecting a payment method, optionally identifying a new card (by\nBIN) or a saved card (by account reference). A non-card method (e.g. Cash)\nomits both `bin` and `accountId`. When `accountId` is given, `bin` may also\nbe given to match the saved card against a BIN-specific installment option;\na `bin` that does not match the saved card's own BIN is rejected with\nMISMATCHED_BIN_FOR_SAVED_CARD.\n\"\"\"\ninput PaymentMethodSelectInput {\n \"\"\"The method to select (PaymentMethod.id).\"\"\"\n methodId: String!\n\n \"\"\"\n A newly-entered card's BIN, once detected. Omit for a saved card or a non-card method.\n \"\"\"\n bin: String\n\n \"\"\"\n A saved card's account reference (SavedCard.accountId). Omit for a new card or a non-card method.\n \"\"\"\n accountId: String\n}\n\n\"\"\"Error codes for the paymentMethodSelect mutation.\"\"\"\nenum PaymentMethodSelectErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"The provided methodId does not match any entry in Payment.methods.\"\"\"\n INVALID_METHOD_ID\n\n \"\"\"The provided accountId does not match any entry in Payment.savedCards.\"\"\"\n INVALID_SAVED_CARD_ACCOUNT_ID\n\n \"\"\"The bin does not match the BIN of the saved card named by accountId.\"\"\"\n MISMATCHED_BIN_FOR_SAVED_CARD\n\n \"\"\"\n The bin is not 1 to 8 numeric digits — a BIN identifies an issuer range, never a card number.\n \"\"\"\n INVALID_BIN_FORMAT\n\n \"\"\"\n Never returned. The input once allowed at most one of bin / accountId; both are\n now accepted so a saved card can be matched to an installment option by its own\n BIN. Retained per ADR-00010 (additive-only with a deprecation window).\n \"\"\"\n CONFLICTING_BIN_AND_ACCOUNT_ID @deprecated(reason: \"Never returned; bin and accountId may now be sent together.\") @deprecationDate(deletionDate: \"2027-08-06\")\n}\n\n\"\"\"A domain error returned from the paymentMethodSelect mutation.\"\"\"\ntype PaymentMethodSelectError {\n \"\"\"Machine-readable error code.\"\"\"\n code: PaymentMethodSelectErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the paymentMethodSelect mutation.\"\"\"\ntype PaymentMethodSelectPayload {\n \"\"\"The updated payment state, or null if errors occurred.\"\"\"\n payment: Payment\n\n \"\"\"\n The current billing address, echoed for convenience. Null when same-as-delivery is in effect.\n \"\"\"\n billingAddress: Address\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [PaymentMethodSelectError!]!\n}\n\n\"\"\"\nInput for selecting an installment count for the currently-selected method.\n\"\"\"\ninput PaymentInstallmentSelectInput {\n \"\"\"Number of installments to select (1 = single payment, no interest).\"\"\"\n count: Int!\n}\n\n\"\"\"Error codes for the paymentInstallmentSelect mutation.\"\"\"\nenum PaymentInstallmentSelectErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n No payment method has been selected yet (paymentMethodSelect was never called).\n \"\"\"\n MISSING_PAYMENT_METHOD_SELECTION\n\n \"\"\"\n The count does not match any tier in the resolvable installment option.\n \"\"\"\n INVALID_INSTALLMENT_COUNT\n}\n\n\"\"\"A domain error returned from the paymentInstallmentSelect mutation.\"\"\"\ntype PaymentInstallmentSelectError {\n \"\"\"Machine-readable error code.\"\"\"\n code: PaymentInstallmentSelectErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"\nPayload returned by the paymentInstallmentSelect mutation. Includes the\nrecomputed cart so the order total (with any interest) updates in the same\nround trip.\n\"\"\"\ntype PaymentInstallmentSelectPayload {\n \"\"\"The updated payment state, or null if errors occurred.\"\"\"\n payment: Payment\n\n \"\"\"\n The recomputed cart, or null if errors occurred. Carries the order total with any installment interest applied.\n \"\"\"\n cart: Cart\n\n \"\"\"\n The current billing address, echoed for convenience. Null when same-as-delivery is in effect.\n \"\"\"\n billingAddress: Address\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [PaymentInstallmentSelectError!]!\n}\n\n\"\"\"\nInput for setting the billing address. When sameAsDelivery is true, every\nother field is ignored and no upstream write happens (VTEX's own default:\nan absent invoiceData falls back to the delivery address).\n\"\"\"\ninput PaymentBillingAddressUpdateInput {\n \"\"\"\n When true, billing uses the delivery address; every field below is ignored.\n \"\"\"\n sameAsDelivery: Boolean!\n\n \"\"\"Address classification for the billing address.\"\"\"\n addressType: String\n\n \"\"\"\n Postal code for the billing address. Required when sameAsDelivery is false.\n \"\"\"\n postalCode: String\n\n \"\"\"\n ISO 3166-1 alpha-3 country code (e.g. \"BRA\"). Required when sameAsDelivery is false.\n \"\"\"\n country: String\n\n \"\"\"Street name.\"\"\"\n street: String\n\n \"\"\"Street number.\"\"\"\n number: String\n\n \"\"\"Additional address detail (apartment, suite, etc.).\"\"\"\n complement: String\n\n \"\"\"Neighborhood / district.\"\"\"\n neighborhood: String\n\n \"\"\"City name.\"\"\"\n city: String\n\n \"\"\"State / province code.\"\"\"\n state: String\n\n \"\"\"\n A landmark reference. Sent upstream but not echoed back on billingAddress (Decision 11).\n \"\"\"\n reference: String\n}\n\n\"\"\"Error codes for the paymentBillingAddressUpdate mutation.\"\"\"\nenum PaymentBillingAddressUpdateErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"sameAsDelivery is false but postalCode or country was omitted.\"\"\"\n MISSING_BILLING_ADDRESS_FIELDS\n\n \"\"\"\n The country is not an ISO 3166-1 alpha-3 code (three capital letters). Refused before any upstream call.\n \"\"\"\n INVALID_COUNTRY_CODE\n}\n\n\"\"\"A domain error returned from the paymentBillingAddressUpdate mutation.\"\"\"\ntype PaymentBillingAddressUpdateError {\n \"\"\"Machine-readable error code.\"\"\"\n code: PaymentBillingAddressUpdateErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the paymentBillingAddressUpdate mutation.\"\"\"\ntype PaymentBillingAddressUpdatePayload {\n \"\"\"\n The billing address just written, or null when same-as-delivery is in effect.\n \"\"\"\n billingAddress: Address\n\n \"\"\"\n The updated payment state, or null if errors occurred, or when same-as-delivery is in effect, where no upstream write happens.\n \"\"\"\n payment: Payment\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [PaymentBillingAddressUpdateError!]!\n}\n\n\"\"\"\nError codes for the paymentStartTransaction mutation. Login-required VTEX\nbusiness codes (CHK0087, CHK003, ORD062) and generic infrastructure failures\nare never in this enum — per ADR-00007 they surface as top-level GraphQL\nerrors (UNAUTHORIZED / UPSTREAM_ERROR), not userErrors.\n\"\"\"\nenum PaymentStartTransactionErrorCode {\n \"\"\"No cart context was provided (x-order-form-id header/cookie).\"\"\"\n MISSING_CART_CONTEXT\n\n \"\"\"The cart was not found.\"\"\"\n CART_NOT_FOUND\n\n \"\"\"\n No payment method has been selected yet (paymentMethodSelect was never called).\n \"\"\"\n MISSING_PAYMENT_METHOD_SELECTION\n\n \"\"\"\n VTEX opened the transaction but named no merchant transaction on it, so\n neither the transaction id the send-payments POST addresses nor the merchant\n name its body carries exists. The transaction is open upstream but cannot be\n paid; the legacy checkout refuses the same state.\n \"\"\"\n INVALID_TRANSACTION_DATA\n}\n\n\"\"\"A domain error returned from the paymentStartTransaction mutation.\"\"\"\ntype PaymentStartTransactionError {\n \"\"\"Machine-readable error code.\"\"\"\n code: PaymentStartTransactionErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"\nPayload returned by paymentStartTransaction: everything the browser needs to\nrun the direct-to-gateway send-payments call (out of scope here) and the\nlater gatewayCallback. The transaction cookies VTEX issues are set on the\nHTTP response (Set-Cookie), not exposed as a field.\n\"\"\"\ntype PaymentStartTransactionPayload {\n \"\"\"The opened transaction's id. Null if errors occurred.\"\"\"\n transactionId: String\n\n \"\"\"\n Name of the merchant responsible for the sale, as the gateway call's payment\n `id` and `transaction.merchantName`. Projected from the same\n merchantTransactions entry transactionId comes from — VTEX returns one entry\n per seller, so a multi-seller order carries more than one and only the first\n is exposed here (see specs/payment-transaction-payload-gaps.md, Decision 1).\n Null if errors occurred.\n \"\"\"\n merchantName: String\n\n \"\"\"\n Origin of the payment gateway this transaction must be paid at, e.g.\n \"https://mystore.vtexvault.com\" — the base the browser's direct-to-gateway\n send-payments POST appends its own path to. Always `https://{account}.vtexvault.com`,\n derived from the request's account alone: no upstream field and no caller-supplied\n value is an input, since this field decides where card data is sent. Null when the\n account is not a single DNS label, and if errors occurred.\n \"\"\"\n gatewayUrl: String\n\n \"\"\"\n The order group, needed for the gateway call's orderId parameter and for gatewayCallback's input.\n \"\"\"\n orderGroup: String\n\n \"\"\"The shopper's user profile id, needed for the gateway call.\"\"\"\n userProfileId: String\n\n \"\"\"\n The payment amount for this transaction, as a decimal (e.g. \"100.00\") for\n display. The gateway body wants minor units — read valueInCents for that,\n never a client-side multiplication of this amount.\n \"\"\"\n value: Money\n\n \"\"\"\n The same payment amount in the currency's minor units (e.g. 10000 for\n \"100.00\"), exactly as VTEX returned it and as this mutation sent it upstream.\n The unit the gateway body's `value`/`installmentValue` fields expect. Null if\n errors occurred.\n \"\"\"\n valueInCents: Int\n\n \"\"\"\n Pre-interest base amount this transaction was opened against, in the\n currency's minor units — the gateway body's `referenceValue`. Equals\n valueInCents when no interest applies. Null if errors occurred.\n \"\"\"\n referenceValueInCents: Int\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [PaymentStartTransactionError!]!\n}\n\n\"\"\"\nInput for finalizing a transaction after the direct-to-gateway call\nsucceeded.\n\"\"\"\ninput PaymentGatewayCallbackInput {\n \"\"\"The order group returned by paymentStartTransaction.\"\"\"\n orderGroup: String!\n\n \"\"\"\n The resume flag. False (the default) on the first pass; true when the shopper\n returns from the bank's challenge page, so the callback accepts the challenge.\n \"\"\"\n acceptChallenges: Boolean = false\n}\n\n\"\"\"\nError codes for the paymentGatewayCallback mutation. Login-required VTEX\nbusiness codes (CHK0087, CHK003, ORD062) and generic infrastructure failures\nare never in this enum — per ADR-00007 they surface as top-level GraphQL\nerrors (UNAUTHORIZED / UPSTREAM_ERROR), not userErrors.\n\"\"\"\nenum PaymentGatewayCallbackErrorCode {\n \"\"\"\n The transaction cookies (Vtex_CHKO_Auth, CheckoutDataAccess) were not present on this request — paymentStartTransaction was not called first, or the five-minute window lapsed.\n \"\"\"\n MISSING_TRANSACTION_COOKIES\n\n \"\"\"\n VTEX answered 428: the payment needs a challenge step. When `redirectUrl` is set the shopper must be sent there; when it is null the challenge uses a mechanism this API does not hand over (for example a payment app).\n \"\"\"\n REDIRECT_REQUIRED\n}\n\n\"\"\"A domain error returned from the paymentGatewayCallback mutation.\"\"\"\ntype PaymentGatewayCallbackError {\n \"\"\"Machine-readable error code.\"\"\"\n code: PaymentGatewayCallbackErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Payload returned by the paymentGatewayCallback mutation.\"\"\"\ntype PaymentGatewayCallbackPayload {\n \"\"\"\n The finalized order group, echoed back on success. Null if errors occurred.\n \"\"\"\n orderId: String\n\n \"\"\"\n The bank's challenge page; set only when VTEX answered 428 with a usable http(s) URL.\n \"\"\"\n redirectUrl: String\n\n \"\"\"Domain errors that occurred during the operation.\"\"\"\n userErrors: [PaymentGatewayCallbackError!]!\n}\n\n\"\"\"\nThe installment plan a settled order was charged in. Projected from the\nplaced-order payment record, which carries the count and the settled total\nonly — never a per-installment amount and never an interest rate, unlike the\nlive order form behind `Payment.selectedInstallment`. That asymmetry is\nupstream and permanent (see specs/settle-and-project-the-placed-order.md,\nDecision 2).\n\"\"\"\ntype OrderInstallment\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Number of installments the order was charged in. 1 = single payment.\"\"\"\n count: Int!\n\n \"\"\"\n Total charged across all installments, including any interest, in the\n order's currency, or the request's when no seller order carries one, as the\n order's other amounts are. Null when the settled record carries no amount.\n \"\"\"\n total: Money\n}\n\n\"\"\"\nA settled order's payment recap. Read-only; projected from VTEX's\nsettled-order source rather than the live order form, so it keeps resolving\nafter the order form itself stops existing. Carries no sensitive field: no\nfull card number or CVV is ever read for this projection (RFC D13).\n\"\"\"\ntype Order implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Globally unique identifier (Relay Node ID), encoded from the order group.\n \"\"\"\n id: ID!\n\n \"\"\"\n The purchase's payment outcome, shared by its seller orders. The banner shows this one. UNKNOWN when\n the seller orders disagree.\n \"\"\"\n status: OrderStatus!\n\n \"\"\"\n The distinct payments of the purchase, each counted once across its seller orders. They follow the\n seller orders sorted by order id, and within a transaction the order the order system lists them\n in, which this API does not change. Empty when the order carries no payment.\n \"\"\"\n payments: [OrderPayment!]!\n\n \"\"\"\n Card network / payment-system display name of the first payment (e.g. \"Visa\", or a non-card\n method's own name such as \"Boleto Bancário\"). Kept for the payment recap; prefer payments. Null\n when the settled order carries no payment record to project one from.\n \"\"\"\n network: String\n\n \"\"\"\n Last four digits of the card used by the first payment, when the payment method was a card.\n Present for both a saved and a freshly-entered card — unlike Payment (T1),\n where last four is a saved-card-only field. Kept for the payment recap; prefer payments. Null for\n a non-card method or when no payment record exists to project one from.\n \"\"\"\n lastFourDigits: String\n\n \"\"\"Always null: the order system sends no card label.\"\"\"\n label: String @deprecated(reason: \"The order system sends no card label; always null.\") @deprecationDate(deletionDate: \"2027-03-31\")\n\n \"\"\"\n The installment plan of the first payment. Kept for the payment recap; prefer payments. Null when\n the settled record reports none.\n \"\"\"\n installment: OrderInstallment\n\n \"\"\"\n Who bought, as the order recorded them at placement, not the live profile. Masked (e.g.\n \"A*** S***\") when personalDataVisible is false. Null when the order carries no profile.\n \"\"\"\n customer: OrderCustomer\n\n \"\"\"\n Whether the order system returned the order's personal data unmasked: the customer, and the\n addresses, invoice and payment details it masks under the same verdict. False means masked, and is\n also the answer when the order system does not say.\n \"\"\"\n personalDataVisible: Boolean!\n\n \"\"\"\n The order group: the reference the shopper reads and support resolves. One per purchase, however\n many sellers.\n \"\"\"\n reference: String!\n\n \"\"\"\n When the purchase was placed: the earliest creation date across its seller orders. Null when none\n carries one.\n \"\"\"\n placedAt: DateTime\n\n \"\"\"\n What the purchase costs: the sum of its seller orders' totals. Null when a seller order has none.\n \"\"\"\n total: Money\n\n \"\"\"\n The purchase's money lines: each line summed across seller orders, keyed on (id, name).\n \"\"\"\n totals: [CartTotalizer!]!\n\n \"\"\"\n The purchase split by seller, one entry per seller order, sorted by orderId.\n \"\"\"\n sellerOrders: [SellerOrder!]!\n\n \"\"\"\n Every item of the purchase grouped by shipping method, channel and relative estimate, across\n sellers; SmartCheckout uses the same key within each seller order.\n \"\"\"\n deliveries: [OrderDelivery!]!\n}\n\n\"\"\"\nItems that arrive together: the same shipping method, channel and relative estimate.\n\"\"\"\ntype OrderDelivery\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Encoded from the grouping key and its scope, the order group.\"\"\"\n id: ID!\n\n \"\"\"\n The shipping method, as the shopper chose it: its name, channel and relative estimate. Its price\n is one line's; use price. Null when the order system omits the method or its estimate is\n unreadable.\n \"\"\"\n shippingOption: OrderShippingOption\n\n \"\"\"\n The group's dated promise: its latest line's, so it covers every item. Null when no line carries a\n date.\n \"\"\"\n estimatedDate: DateTime\n\n \"\"\"\n What the group's shipping cost: the sum of its lines' shipping prices. A line without a readable\n method adds zero, so with shippingOption null this zero is unknown, never free shipping.\n \"\"\"\n price: Money!\n\n \"\"\"\n What the group's shipping would have cost without discounts: the sum of its lines' list prices,\n read as price is.\n \"\"\"\n listPrice: Money!\n\n \"\"\"\n Where the group ships: its first line's address. The address is not part of the grouping key: the\n cart delivers to one address, so a group's lines share it. Each line's own is on\n items.deliveryAddress. Masked under personalDataVisible.\n \"\"\"\n address: Address\n\n \"\"\"\n The pickup point's name: its first line's. Null unless the group is picked up.\n \"\"\"\n pickupPointName: String\n\n \"\"\"The number of items in the group.\"\"\"\n itemCount: Int!\n\n \"\"\"The group's items, in the order of the seller orders.\"\"\"\n items: [OrderItem!]!\n}\n\n\"\"\"\nThe shipping method the shopper chose, as the order recorded it. Read from the order, never from the\ncart.\n\"\"\"\ntype OrderShippingOption\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Encoded from the order group, the line's uniqueId and the SLA id.\"\"\"\n id: ID!\n\n \"\"\"\n The method's name, e.g. Motoboy. The SLA id when the order system sends no name.\n \"\"\"\n name: String!\n\n \"\"\"Delivery channel: home delivery or pickup.\"\"\"\n channel: DeliveryChannel!\n\n \"\"\"The method's relative estimate, e.g. 7 business days.\"\"\"\n shippingEstimate: ShippingEstimate!\n\n \"\"\"What this line's shipping cost. Zero when free shipping applies.\"\"\"\n price: Money!\n\n \"\"\"What this line's shipping would have cost without discounts.\"\"\"\n listPrice: Money!\n}\n\n\"\"\"\nThe payment outcome of a settled order (PRD UQ-2), mapped from the order's Checkout workflow state.\n\"\"\"\nenum OrderStatus {\n \"\"\"The payment was approved, or the order has moved past approval.\"\"\"\n APPROVED\n\n \"\"\"The payment is not decided yet, under anti-fraud review included.\"\"\"\n PENDING\n\n \"\"\"The payment was denied, or the order was cancelled.\"\"\"\n DENIED\n\n \"\"\"A state the page does not classify. It asserts no outcome.\"\"\"\n UNKNOWN\n}\n\n\"\"\"One payment of a settled purchase. No id: it lives inside its Order.\"\"\"\ntype OrderPayment\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Card network / payment-system display name, e.g. \"Visa\". Null when the order system sends none.\n \"\"\"\n network: String\n\n \"\"\"\n Last four digits of the card, when the payment method was a card. Never the card number.\n \"\"\"\n lastFourDigits: String\n\n \"\"\"\n The installment plan of this payment. Null when the settled record reports none.\n \"\"\"\n installment: OrderInstallment\n\n \"\"\"\n What this payment charged, in full: never one instalment. Null only if the order system omits it,\n which it does not today.\n \"\"\"\n value: Money\n}\n\n\"\"\"One seller's part of a purchase: its own seller, totals and items.\"\"\"\ntype SellerOrder\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Encoded from orderId.\"\"\"\n id: ID!\n\n \"\"\"The seller order's number, e.g. 1667094470146-01.\"\"\"\n orderId: String!\n\n \"\"\"\n The seller fulfilling this order. Null only when neither sellers nor any readable item names one.\n \"\"\"\n seller: CartSellerRef\n\n \"\"\"\n What this seller order costs. Null when the order system does not send it.\n \"\"\"\n total: Money\n\n \"\"\"This seller order's money lines. The cart's own totalizer type.\"\"\"\n totals: [CartTotalizer!]!\n\n \"\"\"\n Every line of this seller order. Children of a composition nest under their parent.\n \"\"\"\n items: [OrderItem!]!\n}\n\n\"\"\"\nOne purchased line of a settled order. The fields mean what they mean on CartItem.\n\"\"\"\ntype OrderItem\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Encoded from the order group and the line's uniqueId.\"\"\"\n id: ID!\n\n \"\"\"\n The SKU as the cart shows it: name, image, seller, availability and variation.\n \"\"\"\n variant: CartVariant!\n\n \"\"\"\n The product the SKU belongs to: title, detail URL, categories and brand.\n \"\"\"\n product: CartProduct!\n\n \"\"\"How many units were bought.\"\"\"\n quantity: Int!\n\n \"\"\"The unit price at placement, before discounts.\"\"\"\n price: Money!\n\n \"\"\"Zero when the line had no list price.\"\"\"\n listPrice: Money!\n\n \"\"\"The unit price the shopper paid.\"\"\"\n sellingPrice: Money!\n\n \"\"\"The line's own total, as the cart computes it.\"\"\"\n ownTotal: Money!\n\n \"\"\"The line's own total plus its compositions'.\"\"\"\n total: Money!\n\n \"\"\"Whether the line is a gift.\"\"\"\n gift: Boolean!\n\n \"\"\"The featured promotions that discounted this line.\"\"\"\n discounts: [CartItemDiscount!]!\n\n \"\"\"The values the shopper entered for the line's input assemblies.\"\"\"\n customizations: [CartItemCustomization!]!\n\n \"\"\"The add-on services and bundle children nested under this line.\"\"\"\n compositions: [CartItemComposition!]!\n\n \"\"\"\n The shipping method the shopper chose for this line. Null when the order system omits it or its\n estimate is unreadable.\n \"\"\"\n selectedShippingOption: OrderShippingOption\n\n \"\"\"\n Where this line ships: the delivery address, or the pickup point's. Masked under\n personalDataVisible.\n \"\"\"\n deliveryAddress: Address\n\n \"\"\"\n The pickup point's name, e.g. \"RJ - Botafogo Praia Shopping\". Null unless the line is picked up.\n \"\"\"\n pickupPointName: String\n\n \"\"\"The dated promise. Null when the order system has not set one.\"\"\"\n estimatedDeliveryDate: DateTime\n}\n\n\"\"\"\nThe customer of a settled order, as the order recorded them at placement. The same fields as\nClientProfile, which is the live order form's profile.\n\"\"\"\ntype OrderCustomer\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Email address.\"\"\"\n email: String\n\n \"\"\"First name.\"\"\"\n firstName: String\n\n \"\"\"Last name.\"\"\"\n lastName: String\n\n \"\"\"Phone number.\"\"\"\n phone: String\n\n \"\"\"Identity document number.\"\"\"\n document: String\n}\n\n\"\"\"\nThe address fields a postal code resolves to. Each is null when the platform\nleft it blank.\n\"\"\"\ntype PostalCodeAddress\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"The city, as the platform names it.\"\"\"\n city: String\n\n \"\"\"The state or region, as the platform codes it (`SP`, `NY`).\"\"\"\n state: String\n\n \"\"\"The street. Null where the platform resolves codes to an area only.\"\"\"\n street: String\n\n \"\"\"The neighborhood. Null where the platform resolves none.\"\"\"\n neighborhood: String\n}\n\n\"\"\"\nThe authentication methods an account enables, read before rendering a sign-in form.\n\"\"\"\ntype SessionAuthProviders {\n \"\"\"Whether classic email and password sign-in is enabled.\"\"\"\n passwordAuthentication: Boolean!\n\n \"\"\"Whether email access-code (passwordless) sign-in is enabled.\"\"\"\n accessKeyAuthentication: Boolean!\n}\n\n\"\"\"\nA typed, expected failure of a login attempt. Distinct from the top-level\nUNAUTHORIZED gate: this is a domain outcome (the request was understood, the\nbusiness rejected it), never an infrastructure failure.\n\"\"\"\ntype SessionError {\n \"\"\"Machine-readable error code for the login-attempt failure.\"\"\"\n code: SessionErrorCode!\n\n \"\"\"Human-readable message describing the failure.\"\"\"\n message: String!\n}\n\n\"\"\"The set of login-attempt failure codes a session mutation can return.\"\"\"\nenum SessionErrorCode {\n \"\"\"Email or password was not accepted by VTEX ID.\"\"\"\n INVALID_CREDENTIALS\n\n \"\"\"The access code was wrong or expired.\"\"\"\n INVALID_ACCESS_CODE\n\n \"\"\"Too many access codes were requested for this email.\"\"\"\n ACCESS_CODE_SEND_LIMIT\n\n \"\"\"The user is blocked by VTEX ID.\"\"\"\n BLOCKED\n\n \"\"\"\n BFF-side guard: the request lacked the auth context it needed (e.g. a missing\n authenticationToken). Not a VTEX outcome.\n \"\"\"\n MISSING_AUTH_CONTEXT\n}\n\n\"\"\"\nThe login / logout result, projected from the one refreshed order form. session,\ncart, clientProfile and clientPreferences are normalized Relay Nodes (keyed on\nthe orderFormId) so Apollo merges them into the cache and the client updates\nreactively with no refetch.\n\"\"\"\ntype SessionLoginPayload {\n \"\"\"The session after the attempt; loggedIn is true only on success.\"\"\"\n session: Session!\n\n \"\"\"The cart projected from the refreshed order form.\"\"\"\n cart: Cart!\n\n \"\"\"\n The client profile projected from the refreshed order form; the entity always\n resolves, its fields are null when unset.\n \"\"\"\n clientProfile: ClientProfile!\n\n \"\"\"\n The client preferences projected from the refreshed order form; the entity\n always resolves, its fields are null when unset.\n \"\"\"\n clientPreferences: ClientPreferences!\n\n \"\"\"Typed login-attempt failures. Empty on success.\"\"\"\n userErrors: [SessionError!]!\n}\n\n\"\"\"The result of requesting an email access code.\"\"\"\ntype SessionRequestAccessCodePayload {\n \"\"\"\n Opaque token to hand back to sessionValidateAccessCode. Null when the request\n failed.\n \"\"\"\n authenticationToken: String\n\n \"\"\"Typed request failures. Empty on success.\"\"\"\n userErrors: [SessionError!]!\n}\n\n\"\"\"Credentials for a classic email + password sign-in.\"\"\"\ninput SessionPasswordLoginInput {\n \"\"\"The shopper's email address.\"\"\"\n email: String!\n\n \"\"\"\n The shopper's password. Proxied to VTEX ID over TLS, never logged or persisted.\n \"\"\"\n password: String!\n}\n\n\"\"\"The email to send an access code to.\"\"\"\ninput SessionRequestAccessCodeInput {\n \"\"\"The shopper's email address the access code is sent to.\"\"\"\n email: String!\n}\n\n\"\"\"An access code plus the authenticationToken it was issued against.\"\"\"\ninput SessionValidateAccessCodeInput {\n \"\"\"The shopper's email address.\"\"\"\n email: String!\n\n \"\"\"The access code the shopper received by email.\"\"\"\n accessCode: String!\n\n \"\"\"\n The opaque token returned by sessionRequestAccessCode, handed back unchanged.\n \"\"\"\n authenticationToken: String!\n}\n\n\"\"\"\nThe shopper's active region. Both fields are nullable: a shopper who has never\nsubmitted a postal code has no region, and a submitted postal code does not\nalways resolve to a known city.\n\"\"\"\ntype Region\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Stable identity for cache normalization. Not a Relay Node ID: a region is\n session-scoped shopper state, never addressable by global id.\n \"\"\"\n id: ID!\n\n \"\"\"The active postal code, formatted for display. Null when none is set.\"\"\"\n postalCode: String\n\n \"\"\"\n The city the postal code resolves to. Null when the postal code is unknown to\n the resolver, which is a normal state and not an error.\n \"\"\"\n city: String\n}\n\n\"\"\"\nWhy a region write was refused. Stable across locales, so a client can render\neach case in the shopper's language instead of echoing the server's message.\n\"\"\"\nenum RegionErrorCode {\n \"\"\"The value did not normalize to a complete postal code.\"\"\"\n INVALID_POSTAL_CODE\n\n \"\"\"The postal code is complete but falls outside the served ranges.\"\"\"\n UNSERVED_POSTAL_CODE\n}\n\n\"\"\"\nA typed, expected failure of a region write: the request was understood and the\nbusiness rejected it. Distinct from an infrastructure failure, which surfaces as\na top-level GraphQL error.\n\"\"\"\ntype RegionError {\n \"\"\"Which rule refused the postal code.\"\"\"\n code: RegionErrorCode!\n\n \"\"\"\n Human-readable message describing why the postal code was rejected. Written in\n English by the server; a client should key its copy on `code` instead.\n \"\"\"\n message: String!\n}\n\n\"\"\"The result of submitting a postal code.\"\"\"\ntype RegionUpdatePayload {\n \"\"\"\n The region after the write. Null when the postal code was rejected, leaving\n any previously active region untouched.\n \"\"\"\n region: Region\n\n \"\"\"Typed rejections. Empty on success.\"\"\"\n errors: [RegionError!]!\n}\n\n\"\"\"\nA shopper's persistent, cart-independent profile. Distinct from checkout's\norder-form-scoped ClientProfile.\n\"\"\"\ntype CustomerProfile\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Stable identity for cache normalization. Not a Relay Node ID: the profile is\n session-scoped shopper state, derived from the session and never addressable\n by a global id (no id argument, no CustomerProfile entry in Query.node).\n \"\"\"\n id: ID!\n\n \"\"\"Given name. Null when unset.\"\"\"\n firstName: String\n\n \"\"\"Family name. Null when unset.\"\"\"\n lastName: String\n\n \"\"\"\n The shopper's login identifier. Read-only — excluded from the update input by construction.\n \"\"\"\n email: String\n\n \"\"\"Primary contact phone. Null when unset.\"\"\"\n homePhone: String\n\n \"\"\"Mobile contact phone. Null when unset.\"\"\"\n mobilePhone: String\n\n \"\"\"National tax ID. Null when unset.\"\"\"\n document: String\n\n \"\"\"\n The tax ID's type, so the control can adapt to the shopper's region. Null when unset.\n \"\"\"\n documentType: String\n\n \"\"\"Date of birth. Null when unset.\"\"\"\n birthDate: String\n\n \"\"\"\n Free-form gender value; the store configures the options it offers. Null when unset.\n \"\"\"\n gender: String\n}\n\n\"\"\"A typed, expected failure of a profile write.\"\"\"\ntype CustomerProfileError {\n \"\"\"Machine-readable code for the write failure.\"\"\"\n code: CustomerProfileErrorCode!\n\n \"\"\"A safe message. Never echoes a submitted profile value.\"\"\"\n message: String!\n}\n\n\"\"\"The set of expected, typed profile-write failure codes.\"\"\"\nenum CustomerProfileErrorCode {\n \"\"\"\n The account's profile schema refused one or more submitted values. It does not\n attribute the refusal to a cause — a required field left empty and a malformed\n value are not distinguishable until the upstream rejection body is\n characterised, at which point a narrower code joins this one.\n \"\"\"\n INVALID_VALUE\n}\n\n\"\"\"\nEvery field is optional: provide only what the shopper edited. An empty string\nis an explicit erase; an omitted field is left untouched. A `null` is neither —\nit counts as not provided, so nulling an untouched field cannot wipe it.\n\"\"\"\ninput CustomerProfileUpdateInput {\n \"\"\"Given name to write. Omit to leave unchanged; empty string to erase.\"\"\"\n firstName: String\n\n \"\"\"Family name to write. Omit to leave unchanged; empty string to erase.\"\"\"\n lastName: String\n\n \"\"\"\n Primary contact phone to write. Omit to leave unchanged; empty string to erase.\n \"\"\"\n homePhone: String\n\n \"\"\"\n Mobile contact phone to write. Omit to leave unchanged; empty string to erase.\n \"\"\"\n mobilePhone: String\n\n \"\"\"\n National tax ID to write. Omit to leave unchanged; empty string to erase.\n \"\"\"\n document: String\n\n \"\"\"Tax ID type to write. Omit to leave unchanged; empty string to erase.\"\"\"\n documentType: String\n\n \"\"\"\n Date of birth to write. Omit to leave unchanged; empty string to erase.\n \"\"\"\n birthDate: String\n\n \"\"\"Gender value to write. Omit to leave unchanged; empty string to erase.\"\"\"\n gender: String\n}\n\n\"\"\"\nThe result of a profile write, carrying the merged profile and any typed\nfailures.\n\"\"\"\ntype CustomerProfileUpdatePayload {\n \"\"\"\n The profile after the write, so a reactive fragment re-delivers without a refetch.\n \"\"\"\n customerProfile: CustomerProfile!\n\n \"\"\"Typed, expected write failures. Empty on success.\"\"\"\n userErrors: [CustomerProfileError!]!\n}\n\n\"\"\"\nAn address the shopper saved to their account. Read from the shopper's own\nprofile, never through a cart. Every field it shares with checkout's Address is\nspelled the same way.\n\"\"\"\ntype CustomerAddress implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Global id. Resolves through Query.node only inside the signed-in shopper's own\n addresses; any other id resolves to null.\n \"\"\"\n id: ID!\n\n \"\"\"\n ISO 3166-1 alpha-3 country code, as stored. Null when the stored address has\n none, as an address another system wrote can; it is listed so the shopper can\n still see and remove it.\n \"\"\"\n country: String\n\n \"\"\"Who receives deliveries at this address. Null when unset.\"\"\"\n recipientName: String\n\n \"\"\"Street name. Null when unset.\"\"\"\n street: String\n\n \"\"\"Street number. Null when unset.\"\"\"\n number: String\n\n \"\"\"Additional address detail (apartment, suite, etc.). Null when unset.\"\"\"\n complement: String\n\n \"\"\"Neighborhood or district. Null when unset.\"\"\"\n neighborhood: String\n\n \"\"\"City. Null when unset.\"\"\"\n city: String\n\n \"\"\"State or province code. Null when unset.\"\"\"\n state: String\n\n \"\"\"Postal code, as stored. Null when unset.\"\"\"\n postalCode: String\n\n \"\"\"A landmark that helps find the address. Null when unset.\"\"\"\n reference: String\n}\n\n\"\"\"\nThe signed-in shopper's saved addresses, held in one object so a client can cache\nand subscribe to the list. It takes no argument and reads only the shopper's own\nlist. Not resolvable through Query.node.\n\"\"\"\ntype CustomerAddressBook\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Stable identity for cache normalization, the same for every request of the\n shopper. Not a Relay Node ID.\n \"\"\"\n id: ID!\n\n \"\"\"\n Every saved address, the same list `customerAddresses` returns. Empty when the\n shopper has none.\n \"\"\"\n addresses: [CustomerAddress!]!\n}\n\n\"\"\"\nA new saved address. `country` is required; every other field is optional to the\nschema, and the country's rules say which ones it needs. A field that is omitted,\n`null`, or an empty string is not written.\n\"\"\"\ninput CustomerAddressCreateInput {\n \"\"\"\n ISO 3166-1 alpha-3 country code. The store must ship to it. Trimmed and upper-cased.\n \"\"\"\n country: String!\n\n \"\"\"Who receives deliveries at this address.\"\"\"\n recipientName: String\n\n \"\"\"Street name.\"\"\"\n street: String\n\n \"\"\"Street number.\"\"\"\n number: String\n\n \"\"\"Additional address detail (apartment, suite, etc.).\"\"\"\n complement: String\n\n \"\"\"Neighborhood or district.\"\"\"\n neighborhood: String\n\n \"\"\"City.\"\"\"\n city: String\n\n \"\"\"State or province code.\"\"\"\n state: String\n\n \"\"\"Postal code. Stored in the country's masked form once it is valid.\"\"\"\n postalCode: String\n\n \"\"\"A landmark that helps find the address.\"\"\"\n reference: String\n}\n\n\"\"\"\nChanges to a saved address. Omit a field, or send `null`, to keep it; send an empty\nstring to erase it. The country cannot change.\n\"\"\"\ninput CustomerAddressUpdateInput {\n \"\"\"\n Who receives deliveries at this address. Omit to leave unchanged; empty string to erase.\n \"\"\"\n recipientName: String\n\n \"\"\"Street name. Omit to leave unchanged; empty string to erase.\"\"\"\n street: String\n\n \"\"\"Street number. Omit to leave unchanged; empty string to erase.\"\"\"\n number: String\n\n \"\"\"\n Additional address detail (apartment, suite, etc.). Omit to leave unchanged; empty string to erase.\n \"\"\"\n complement: String\n\n \"\"\"\n Neighborhood or district. Omit to leave unchanged; empty string to erase.\n \"\"\"\n neighborhood: String\n\n \"\"\"City. Omit to leave unchanged; empty string to erase.\"\"\"\n city: String\n\n \"\"\"\n State or province code. Omit to leave unchanged; empty string to erase.\n \"\"\"\n state: String\n\n \"\"\"\n Postal code. Omit to leave unchanged; empty string to erase. Stored in the country's masked form once it is valid.\n \"\"\"\n postalCode: String\n\n \"\"\"\n A landmark that helps find the address. Omit to leave unchanged; empty string to erase.\n \"\"\"\n reference: String\n}\n\n\"\"\"\nA typed, expected failure of an address write, or of the lookup that precedes it.\n\"\"\"\ntype CustomerAddressError {\n \"\"\"Machine-readable code for the failure.\"\"\"\n code: CustomerAddressErrorCode!\n\n \"\"\"\n A safe, fixed message. Never echoes a submitted value, an id, or a key.\n \"\"\"\n message: String!\n\n \"\"\"\n The field the failure is about. Null when it is about the address as a whole.\n \"\"\"\n field: CustomerAddressField\n}\n\n\"\"\"The set of expected, typed address-write failure codes.\"\"\"\nenum CustomerAddressErrorCode {\n \"\"\"\n The id is malformed, names another entity type, or is not one of the shopper's saved\n addresses. These cases are indistinguishable.\n \"\"\"\n NOT_FOUND\n\n \"\"\"The country's rules require this field and it is empty.\"\"\"\n MISSING_REQUIRED_FIELD\n\n \"\"\"\n The value is not accepted: a bad format, a value outside the country's options, a value\n that is too long, or a country that is not three letters. With no field, the profile\n service refused the address and does not say why.\n \"\"\"\n INVALID_VALUE\n\n \"\"\"The store does not ship to the country.\"\"\"\n UNSUPPORTED_COUNTRY\n}\n\n\"\"\"The address field a failure is about.\"\"\"\nenum CustomerAddressField {\n \"\"\"The `country` field.\"\"\"\n COUNTRY\n\n \"\"\"The `recipientName` field.\"\"\"\n RECIPIENT_NAME\n\n \"\"\"The `street` field.\"\"\"\n STREET\n\n \"\"\"The `number` field.\"\"\"\n NUMBER\n\n \"\"\"The `complement` field.\"\"\"\n COMPLEMENT\n\n \"\"\"The `neighborhood` field.\"\"\"\n NEIGHBORHOOD\n\n \"\"\"The `city` field.\"\"\"\n CITY\n\n \"\"\"The `state` field.\"\"\"\n STATE\n\n \"\"\"The `postalCode` field.\"\"\"\n POSTAL_CODE\n\n \"\"\"The `reference` field.\"\"\"\n REFERENCE\n}\n\n\"\"\"The result of creating a saved address.\"\"\"\ntype CustomerAddressCreatePayload {\n \"\"\"The address as written. Null when `userErrors` is not empty.\"\"\"\n customerAddress: CustomerAddress\n\n \"\"\"Typed, expected failures, one per refused field. Empty on success.\"\"\"\n userErrors: [CustomerAddressError!]!\n}\n\n\"\"\"The result of updating a saved address.\"\"\"\ntype CustomerAddressUpdatePayload {\n \"\"\"\n The address as written, or as stored when the input provided no field. Null when\n `userErrors` is not empty.\n \"\"\"\n customerAddress: CustomerAddress\n\n \"\"\"Typed, expected failures, one per refused field. Empty on success.\"\"\"\n userErrors: [CustomerAddressError!]!\n}\n\n\"\"\"The result of deleting a saved address.\"\"\"\ntype CustomerAddressDeletePayload {\n \"\"\"\n The id of the deleted address, to evict it from a client cache. Null when `userErrors`\n is not empty.\n \"\"\"\n deletedCustomerAddressId: ID\n\n \"\"\"Typed, expected failures. Empty on success.\"\"\"\n userErrors: [CustomerAddressError!]!\n}\n\n\"\"\"\nThe countries the store ships to, read from the platform's freight\nconfiguration. Store-level and identical for every shopper.\n\"\"\"\ntype ShippingCountries\n @cacheControl(maxAge: 3600, scope: PUBLIC)\n{\n \"\"\"\n ISO 3166-1 alpha-3 codes, upper-case, without duplicates, in the order the\n platform lists them. Never empty: when the freight configuration declares no\n country, this holds the sales channel's country and `source` says so.\n \"\"\"\n countries: [String!]!\n\n \"\"\"\n Where `countries` came from. A consumer that judges an address against the\n list must not do so when this is `SALES_CHANNEL_FALLBACK`.\n \"\"\"\n source: ShippingCountriesSource!\n}\n\n\"\"\"The origin of a shipping-country list.\"\"\"\nenum ShippingCountriesSource {\n \"\"\"The store's freight configuration declares these countries.\"\"\"\n PLATFORM\n\n \"\"\"\n The freight configuration declares none, so the list is the sales channel's\n country alone. It is a guess, not a declared delivery area.\n \"\"\"\n SALES_CHANNEL_FALLBACK\n}\n\n\"\"\"A placed order owned by the signed-in shopper.\"\"\"\ntype CustomerOrder implements Node\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Global Node id. Refetch it with `node` or `customerOrder`.\"\"\"\n id: ID!\n\n \"\"\"\n Shopper-facing order number. The same value the list carries as its `id`.\n \"\"\"\n orderNumber: String!\n\n \"\"\"Raw OMS status code.\"\"\"\n status: String!\n\n \"\"\"Whether OMS currently permits whole-order cancellation.\"\"\"\n allowCancellation: Boolean!\n\n \"\"\"Timestamp when the order was placed.\"\"\"\n placedAt: DateTime\n\n \"\"\"Grand total recorded on the order.\"\"\"\n total: Money!\n\n \"\"\"Itemized totals recorded on the order.\"\"\"\n totals: [CustomerOrderTotal!]!\n\n \"\"\"Product lines recorded on the order.\"\"\"\n items: [CustomerOrderItem!]!\n\n \"\"\"Product lines grouped by their selected delivery option.\"\"\"\n deliveryOptions: [CustomerOrderDeliveryOption!]!\n\n \"\"\"Payments recorded on active and historical transactions.\"\"\"\n payments: [CustomerOrderPayment!]!\n\n \"\"\"Whether every package has a recorded delivery confirmation.\"\"\"\n isAllDelivered: Boolean\n\n \"\"\"Earliest estimated delivery timestamp.\"\"\"\n shippingEstimatedDateMin: DateTime\n\n \"\"\"Latest estimated delivery timestamp.\"\"\"\n shippingEstimatedDateMax: DateTime\n\n \"\"\"Timestamp when the order was invoiced.\"\"\"\n invoiceDate: DateTime\n\n \"\"\"\n Order delivery channel. The order-level value when OMS supplies it, otherwise\n the channel every logistics entry agrees on. Null when those entries differ.\n \"\"\"\n selectedDeliveryChannel: String\n\n \"\"\"\n Completion resolved once for this order, with the inputs the single-order payload carries. The list also passes courierStatusFinished, which this payload does not.\n \"\"\"\n completion: CustomerOrderCompletion!\n\n \"\"\"The progress stage this order is in.\"\"\"\n progress: CustomerOrderProgress!\n}\n\n\"\"\"\nOne of the four progress stages a shopper sees. Approval is not a stage.\n\"\"\"\nenum CustomerOrderProgressStage {\n \"\"\"The order was placed and payment has not started.\"\"\"\n PLACED\n\n \"\"\"Payment is pending, awaiting authorization, or denied.\"\"\"\n PAYMENT\n\n \"\"\"Payment was approved and the order is being prepared.\"\"\"\n PROCESSING\n\n \"\"\"The order was invoiced and is on its way or ready for pickup.\"\"\"\n SHIPPING\n}\n\n\"\"\"Progress stage resolved once for one order.\"\"\"\ntype CustomerOrderProgress {\n \"\"\"The stage the order is in. A cancelled order stays on PAYMENT.\"\"\"\n stage: CustomerOrderProgressStage!\n\n \"\"\"True when payment was denied or the order was cancelled.\"\"\"\n paymentFailed: Boolean!\n\n \"\"\"True when the order is cancelled or a cancellation was requested.\"\"\"\n cancelled: Boolean!\n}\n\n\"\"\"One total line recorded on a placed order.\"\"\"\ntype CustomerOrderTotal {\n \"\"\"Stable total category code.\"\"\"\n id: String!\n\n \"\"\"Upstream total name.\"\"\"\n name: String!\n\n \"\"\"Monetary value of this total line.\"\"\"\n value: Money!\n}\n\n\"\"\"One product line recorded on a placed order.\"\"\"\ntype CustomerOrderItem {\n \"\"\"Stable line identifier within the order.\"\"\"\n id: ID!\n\n \"\"\"Product name recorded at purchase time.\"\"\"\n name: String!\n\n \"\"\"Purchased quantity.\"\"\"\n quantity: Int!\n\n \"\"\"Product image recorded at purchase time.\"\"\"\n image: Image\n\n \"\"\"Base unit price recorded on the line.\"\"\"\n price: Money\n\n \"\"\"Unit list price recorded on the line.\"\"\"\n listPrice: Money\n\n \"\"\"Effective unit selling price recorded on the line.\"\"\"\n sellingPrice: Money\n\n \"\"\"\n Line total from the recorded selling price and quantity. Null when that price is absent.\n \"\"\"\n total: Money\n\n \"\"\"\n Tax recorded on the line. Null when OMS omitted it. A recorded zero is kept.\n \"\"\"\n tax: Money\n\n \"\"\"Custom values recorded on the line.\"\"\"\n customFields: [CustomerOrderItemCustomField!]!\n}\n\n\"\"\"A custom value recorded on an order line.\"\"\"\ntype CustomerOrderItemCustomField {\n \"\"\"Custom field name.\"\"\"\n name: String!\n\n \"\"\"Custom field value as recorded on the order.\"\"\"\n value: String!\n}\n\n\"\"\"Products that share one selected delivery option.\"\"\"\ntype CustomerOrderDeliveryOption {\n \"\"\"Selected service-level agreement name.\"\"\"\n selectedSla: String\n\n \"\"\"Address identifier recorded by OMS.\"\"\"\n addressId: String\n\n \"\"\"Delivery or pickup channel.\"\"\"\n deliveryChannel: String\n\n \"\"\"Estimated delivery timestamp for this option.\"\"\"\n shippingEstimateDate: DateTime\n\n \"\"\"Historical address recorded on the order.\"\"\"\n address: CustomerOrderAddress\n\n \"\"\"Product lines assigned to this option.\"\"\"\n items: [CustomerOrderItem!]!\n}\n\n\"\"\"Historical delivery address recorded on an order.\"\"\"\ntype CustomerOrderAddress {\n \"\"\"Recipient name.\"\"\"\n receiverName: String\n\n \"\"\"Postal code.\"\"\"\n postalCode: String\n\n \"\"\"City.\"\"\"\n city: String\n\n \"\"\"State or region.\"\"\"\n state: String\n\n \"\"\"ISO country code.\"\"\"\n country: String\n\n \"\"\"Street.\"\"\"\n street: String\n\n \"\"\"Street number.\"\"\"\n number: String\n\n \"\"\"Neighborhood.\"\"\"\n neighborhood: String\n\n \"\"\"Address complement.\"\"\"\n complement: String\n\n \"\"\"Address reference.\"\"\"\n reference: String\n}\n\n\"\"\"One payment recorded on a placed order.\"\"\"\ntype CustomerOrderPayment {\n \"\"\"Shopper-facing payment method name.\"\"\"\n methodName: String\n\n \"\"\"\n Last four digits recorded on the payment. Null when OMS omitted them or sent a blank.\n \"\"\"\n lastDigits: String\n\n \"\"\"Amount charged through this payment.\"\"\"\n amount: Money!\n\n \"\"\"Number of installments.\"\"\"\n installments: Int\n\n \"\"\"Gift-card redemption code when this payment used one.\"\"\"\n redemptionCode: String\n\n \"\"\"Bank-invoice number already recorded on the payment.\"\"\"\n bankInvoiceNumber: String\n\n \"\"\"Existing bank-invoice print URL.\"\"\"\n bankInvoicePrintUrl: URI\n}\n\n\"\"\"Which slice of the shopper's orders to read.\"\"\"\nenum CustomerOrdersView {\n \"\"\"Every order in the narrowed route result.\"\"\"\n ALL\n\n \"\"\"Orders the completion rule places in ongoing.\"\"\"\n ONGOING\n\n \"\"\"Orders the completion rule places in completed.\"\"\"\n COMPLETED\n\n \"\"\"Orders the completion rule places in cancelled.\"\"\"\n CANCELLED\n}\n\n\"\"\"The view resolved for one list order.\"\"\"\nenum CustomerOrderCompletionView {\n \"\"\"The order is still open.\"\"\"\n ONGOING\n\n \"\"\"The order has reached the shopper, or the shipment window has passed.\"\"\"\n COMPLETED\n\n \"\"\"The cancellation has finished.\"\"\"\n CANCELLED\n}\n\n\"\"\"Label id for the catalogue. The catalogue owns the sentence.\"\"\"\nenum CustomerOrderCompletionLabel {\n \"\"\"A finished cancellation.\"\"\"\n CANCELLED\n\n \"\"\"A cancellation that was requested and has not finished.\"\"\"\n CANCELLATION_REQUESTED\n\n \"\"\"An order that is not yet shipped.\"\"\"\n IN_PROGRESS\n\n \"\"\"Delivery was confirmed.\"\"\"\n DELIVERED\n\n \"\"\"Shipped, or completed from the estimate or the invoice period.\"\"\"\n SHIPPED\n\n \"\"\"Ready for the shopper to collect.\"\"\"\n READY_FOR_PICKUP\n\n \"\"\"Pickup collection was confirmed.\"\"\"\n COLLECTED\n}\n\n\"\"\"View and label resolved once for one list order.\"\"\"\ntype CustomerOrderCompletion {\n \"\"\"The view this order belongs to.\"\"\"\n view: CustomerOrderCompletionView!\n\n \"\"\"The catalogue label id.\"\"\"\n label: CustomerOrderCompletionLabel!\n}\n\n\"\"\"One order on the shopper's list. The detail document is CustomerOrder.\"\"\"\ntype CustomerOrderListItem {\n \"\"\"Shopper-facing order number. Pass this value to customerOrder.\"\"\"\n id: ID!\n\n \"\"\"Raw OMS status code. Empty when the list entry omits it.\"\"\"\n status: String!\n\n \"\"\"When the order was placed.\"\"\"\n placedAt: DateTime\n\n \"\"\"\n Grand total. Null when the list entry omits the value or the currency code.\n \"\"\"\n total: Money\n\n \"\"\"Sum of line quantities. Null when the list entry omits it.\"\"\"\n itemCount: Int\n\n \"\"\"Completion resolved once for this order.\"\"\"\n completion: CustomerOrderCompletion!\n\n \"\"\"Whether every package has a recorded delivery confirmation.\"\"\"\n isAllDelivered: Boolean\n\n \"\"\"Earliest estimated delivery timestamp.\"\"\"\n shippingEstimatedDateMin: DateTime\n\n \"\"\"Latest estimated delivery timestamp.\"\"\"\n shippingEstimatedDateMax: DateTime\n\n \"\"\"Timestamp when the order was invoiced.\"\"\"\n invoiceDate: DateTime\n\n \"\"\"Delivery channel when the list entry carries it.\"\"\"\n selectedDeliveryChannel: String\n}\n\n\"\"\"An edge in the shopper's order list.\"\"\"\ntype CustomerOrderListItemEdge {\n \"\"\"Resume cursor for the order after this one.\"\"\"\n cursor: Cursor!\n\n \"\"\"The list order.\"\"\"\n node: CustomerOrderListItem!\n}\n\n\"\"\"One page of the signed-in shopper's orders.\"\"\"\ntype CustomerOrdersConnection {\n \"\"\"Orders on this page.\"\"\"\n edges: [CustomerOrderListItemEdge!]!\n\n \"\"\"Cursors for the page.\"\"\"\n pageInfo: PageInfo!\n\n \"\"\"Route total for ALL. Classified count so far for a filtered view.\"\"\"\n totalCount: Int!\n\n \"\"\"True only when a filtered walk stopped at 20 upstream pages.\"\"\"\n totalIncomplete: Boolean!\n}\n\n\"\"\"Criteria applied before the page is returned.\"\"\"\ninput CustomerOrdersFilterInput {\n \"\"\"View to return. Defaults to every order.\"\"\"\n view: CustomerOrdersView = ALL\n\n \"\"\"Free-text term sent to the order route.\"\"\"\n text: String\n\n \"\"\"Inclusive start of the placed-date range, as an instant.\"\"\"\n placedFrom: DateTime\n\n \"\"\"Inclusive end of the placed-date range, as an instant.\"\"\"\n placedTo: DateTime\n}\n\n\"\"\"What the shopper submits with a whole-order cancellation.\"\"\"\ninput CustomerOrderCancelInput {\n \"\"\"Optional reason the merchant keeps. Trimmed; at most 500 characters.\"\"\"\n reason: String\n}\n\n\"\"\"The set of expected, typed cancellation failure codes.\"\"\"\nenum CustomerOrderCancelErrorCode {\n \"\"\"\n Checkout refused the cancellation. The order is past the point where the shopper\n can cancel it, or a cancellation was already requested.\n \"\"\"\n ORDER_NOT_CANCELLABLE\n\n \"\"\"\n No order the signed-in shopper can see has this id. A missing id and someone\n else's id are not distinguished.\n \"\"\"\n NOT_FOUND\n}\n\n\"\"\"A typed, expected failure of a cancellation.\"\"\"\ntype CustomerOrderCancelError {\n \"\"\"Machine-readable code for the failure.\"\"\"\n code: CustomerOrderCancelErrorCode!\n\n \"\"\"A fixed message. Never echoes the upstream response.\"\"\"\n message: String!\n}\n\n\"\"\"The result of a cancellation request.\"\"\"\ntype CustomerOrderCancelPayload {\n \"\"\"\n The order re-read after checkout accepted the request. Null on a user error, and\n null when the re-read failed after an accepted request.\n \"\"\"\n customerOrder: CustomerOrder\n\n \"\"\"Typed, expected failures. Empty on success.\"\"\"\n userErrors: [CustomerOrderCancelError!]!\n}\n\n\"\"\"\nThe signed-in shopper's saved cards, read from their persistent checkout\nprofile. Never cart-scoped.\n\"\"\"\ntype CustomerSavedCards\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n Stable identity for cache normalization. Not a Relay Node ID: derived from the\n session, with no id argument and no Query.node entry.\n \"\"\"\n id: ID!\n\n \"\"\"\n Every card the shopper saved, in the platform's order, including expired and\n unusable ones. Empty when none is saved.\n \"\"\"\n cards: [CustomerSavedCard!]!\n}\n\n\"\"\"\nOne of the shopper's saved cards, as the account sees it. Distinct from the\ncart-scoped SavedCard. Never carries a card number beyond its last four digits,\na BIN, or a security code.\n\"\"\"\ntype CustomerSavedCard\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Opaque, stable cache key. Not a Relay Node ID.\"\"\"\n id: ID!\n\n \"\"\"The card network the card belongs to.\"\"\"\n network: CardNetwork!\n\n \"\"\"\n The card number's last four digits. Empty when the platform's record has none.\n \"\"\"\n lastDigits: String!\n\n \"\"\"When the card expires. Null when the platform's record has no date.\"\"\"\n expiry: CardExpiry\n\n \"\"\"\n Whether the card can still be used, derived once here so the UI never re-derives it.\n \"\"\"\n status: CustomerSavedCardStatus!\n}\n\n\"\"\"A card network, such as Visa or Mastercard.\"\"\"\ntype CardNetwork {\n \"\"\"\n The platform's payment-system id for the network. Empty when the record has none.\n \"\"\"\n id: String!\n\n \"\"\"\n Display name of the network (e.g. \"Visa\"). Falls back to the id when the record has no name.\n \"\"\"\n name: String!\n}\n\n\"\"\"A card's expiry month and year.\"\"\"\ntype CardExpiry {\n \"\"\"Expiry month, from 1 to 12.\"\"\"\n month: Int!\n\n \"\"\"Four-digit expiry year.\"\"\"\n year: Int!\n}\n\n\"\"\"Whether a saved card can still be used.\"\"\"\nenum CustomerSavedCardStatus {\n \"\"\"The card has not expired and the platform reports nothing against it.\"\"\"\n USABLE\n\n \"\"\"\n The card's expiry date has passed. Wins over any other platform report.\n \"\"\"\n EXPIRED\n\n \"\"\"The card has not expired, but the platform reports it cannot be used.\"\"\"\n UNUSABLE\n}\n\n\"\"\"\nA card network the store accepts on the request's sales channel, with the\nrules card entry validates a card by. Read from the store's payment systems,\nnever from a cart.\n\"\"\"\ntype CardNetworkRule\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"\n The network. Its id is the platform's payment-system id, the same id a saved\n card's network carries.\n \"\"\"\n network: CardNetwork!\n\n \"\"\"\n Pattern a full card number of this network matches. Null when the store sets none.\n \"\"\"\n numberRegex: String\n\n \"\"\"\n Display mask for the card number, with 9 standing for a digit. Null when the store sets none.\n \"\"\"\n numberMask: String\n\n \"\"\"Pattern the security code matches. Null when the store sets none.\"\"\"\n securityCodeRegex: String\n\n \"\"\"Display mask for the security code. Null when the store sets none.\"\"\"\n securityCodeMask: String\n\n \"\"\"Whether the card asks for a security code.\"\"\"\n securityCodeRequired: Boolean!\n\n \"\"\"Whether the card asks for an expiry date.\"\"\"\n expirationRequired: Boolean!\n\n \"\"\"Whether the card asks for the holder's name.\"\"\"\n holderNameRequired: Boolean!\n\n \"\"\"Whether a cardholder document is required for this network.\"\"\"\n requiresDocument: Boolean!\n\n \"\"\"\n Whether a cardholder document field is shown for this network. Distinct from requiresDocument.\n \"\"\"\n displayDocument: Boolean!\n}\n\n\"\"\"\nOne of the signed-in shopper's saved addresses a new card can be billed to.\n\"\"\"\ntype CardBillingAddress\n @cacheControl(maxAge: 0, scope: PRIVATE)\n{\n \"\"\"Opaque, stable cache key. Not a Relay Node ID.\"\"\"\n id: ID!\n\n \"\"\"\n Name of the person the address belongs to. Null when the platform's record has none.\n \"\"\"\n receiverName: String\n\n \"\"\"Street name. Null when the platform's record has none.\"\"\"\n street: String\n\n \"\"\"Street number. Null when the platform's record has none.\"\"\"\n number: String\n\n \"\"\"\n Complement, such as an apartment. Null when the platform's record has none.\n \"\"\"\n complement: String\n\n \"\"\"Neighborhood. Null when the platform's record has none.\"\"\"\n neighborhood: String\n\n \"\"\"City. Null when the platform's record has none.\"\"\"\n city: String\n\n \"\"\"State or province. Null when the platform's record has none.\"\"\"\n state: String\n\n \"\"\"Postal code. Null when the platform's record has none.\"\"\"\n postalCode: String\n\n \"\"\"\n Country of the address, as the platform stores it (ISO 3166-1 alpha-3).\n \"\"\"\n country: String!\n}\n\n\"\"\"Payload returned by customerSavedCardRemove.\"\"\"\ntype CustomerSavedCardRemovePayload {\n \"\"\"\n The shopper's saved cards after the removal. Null when userErrors is non-empty.\n \"\"\"\n customerSavedCards: CustomerSavedCards\n\n \"\"\"Domain errors that stopped the removal. Empty on success.\"\"\"\n userErrors: [CustomerSavedCardRemoveError!]!\n}\n\n\"\"\"A domain error returned by customerSavedCardRemove.\"\"\"\ntype CustomerSavedCardRemoveError {\n \"\"\"Machine-readable error code.\"\"\"\n code: CustomerSavedCardRemoveErrorCode!\n\n \"\"\"Human-readable error message.\"\"\"\n message: String!\n}\n\n\"\"\"Why a saved card was not removed.\"\"\"\nenum CustomerSavedCardRemoveErrorCode {\n \"\"\"The id names no saved card of the signed-in shopper.\"\"\"\n CARD_NOT_FOUND\n\n \"\"\"\n The platform refused the removal, or the card is still listed after it.\n \"\"\"\n REMOVAL_REFUSED\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar federation__Scope\n\nscalar federation__Policy\n\nscalar federation__ContextFieldValue\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n";
|
|
17934
18306
|
//#endregion
|
|
17935
18307
|
//#region src/cli/core-sdl.ts
|
|
17936
18308
|
/**
|
|
@@ -17961,14 +18333,6 @@ function extensionName(extensionRoot, definition) {
|
|
|
17961
18333
|
return "extension";
|
|
17962
18334
|
}
|
|
17963
18335
|
//#endregion
|
|
17964
|
-
//#region src/cli/host.ts
|
|
17965
|
-
/** The host's esbuild, or a clear error naming the field the host forgot. */
|
|
17966
|
-
function requireEsbuild(host, verb) {
|
|
17967
|
-
const bundler = host?.esbuild;
|
|
17968
|
-
if (!bundler || typeof bundler.build !== "function") throw new Error(`storefront ${verb}: host.esbuild is required — pass \`{ esbuild }\` from the host.`);
|
|
17969
|
-
return bundler;
|
|
17970
|
-
}
|
|
17971
|
-
//#endregion
|
|
17972
18336
|
//#region src/cli/load-declaration.ts
|
|
17973
18337
|
/**
|
|
17974
18338
|
* Resolves the extension's declaration entry point: `src/index.ts` by
|
|
@@ -18006,7 +18370,7 @@ function isDeclaration(value) {
|
|
|
18006
18370
|
*/
|
|
18007
18371
|
const CREATE_REQUIRE_BANNER = "import { createRequire as __storefrontCreateRequire } from 'node:module';\nconst require = __storefrontCreateRequire(import.meta.url);";
|
|
18008
18372
|
/**
|
|
18009
|
-
* Bundles the declaration entry with the
|
|
18373
|
+
* Bundles the declaration entry with the runner's esbuild into
|
|
18010
18374
|
* `.vtex/storefront/load/<hash>.mjs` and returns that path. First-party
|
|
18011
18375
|
* modules are bundled, so `from './schema'` and an `enum` both work without a
|
|
18012
18376
|
* TypeScript loader; packages stay external and resolve from the extension's
|
|
@@ -18025,8 +18389,8 @@ const CREATE_REQUIRE_BANNER = "import { createRequire as __storefrontCreateRequi
|
|
|
18025
18389
|
* first-party module lands on a new file name and is evaluated afresh, while
|
|
18026
18390
|
* an unchanged declaration reuses the module it already has.
|
|
18027
18391
|
*/
|
|
18028
|
-
async function bundleDeclaration(extensionRoot, entryPath,
|
|
18029
|
-
const bundler =
|
|
18392
|
+
async function bundleDeclaration(extensionRoot, entryPath, verb) {
|
|
18393
|
+
const bundler = await loadBundler(verb);
|
|
18030
18394
|
const root = resolve(extensionRoot);
|
|
18031
18395
|
let code;
|
|
18032
18396
|
try {
|
|
@@ -18051,19 +18415,27 @@ async function bundleDeclaration(extensionRoot, entryPath, host, verb) {
|
|
|
18051
18415
|
}
|
|
18052
18416
|
/**
|
|
18053
18417
|
* Loads the extension's `defineSubgraph` declaration: bundles the entry with
|
|
18054
|
-
* the
|
|
18418
|
+
* the runner's esbuild (see `bundleDeclaration`) and `import()`s the bundle.
|
|
18055
18419
|
* `verb` names the caller in every error, because the same failure reads
|
|
18056
18420
|
* differently under `validate`, `build` and `dev`.
|
|
18057
18421
|
*/
|
|
18058
|
-
async function loadDeclaration(extensionRoot,
|
|
18422
|
+
async function loadDeclaration(extensionRoot, verb = "dev") {
|
|
18059
18423
|
const entryPath = resolveDeclarationEntry(extensionRoot);
|
|
18060
18424
|
if (!existsSync(entryPath)) throw new Error(`storefront ${verb}: declaration entry not found at ${entryPath}.`);
|
|
18061
|
-
const bundle = await bundleDeclaration(extensionRoot, entryPath,
|
|
18425
|
+
const bundle = await bundleDeclaration(extensionRoot, entryPath, verb);
|
|
18062
18426
|
const definition = extractDeclaration(await import(pathToFileURL(bundle).href));
|
|
18063
18427
|
if (!definition) throw new Error(`storefront ${verb}: ${entryPath} must \`export default defineSubgraph({ ... })\`. No default export with a \`typeDefs\` property was found.`);
|
|
18064
18428
|
return definition;
|
|
18065
18429
|
}
|
|
18066
18430
|
//#endregion
|
|
18431
|
+
//#region src/cli/log.ts
|
|
18432
|
+
/** The default sink: a caller that passes nothing hears nothing. */
|
|
18433
|
+
const SILENT_LOG = {
|
|
18434
|
+
info: () => void 0,
|
|
18435
|
+
warn: () => void 0,
|
|
18436
|
+
error: () => void 0
|
|
18437
|
+
};
|
|
18438
|
+
//#endregion
|
|
18067
18439
|
//#region ../../node_modules/.pnpm/@apollo+subgraph@2.13.3_graphql@16.13.1/node_modules/@apollo/subgraph/dist/schema-helper/resolverMap.js
|
|
18068
18440
|
var require_resolverMap = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
18069
18441
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -30962,7 +31334,7 @@ var require_ansi_styles = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
30962
31334
|
//#endregion
|
|
30963
31335
|
//#region ../../node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js
|
|
30964
31336
|
var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
30965
|
-
const os
|
|
31337
|
+
const os = __require("os");
|
|
30966
31338
|
const tty = __require("tty");
|
|
30967
31339
|
const hasFlag = require_has_flag();
|
|
30968
31340
|
const { env } = process;
|
|
@@ -30991,7 +31363,7 @@ var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) =>
|
|
|
30991
31363
|
const min = forceColor || 0;
|
|
30992
31364
|
if (env.TERM === "dumb") return min;
|
|
30993
31365
|
if (process.platform === "win32") {
|
|
30994
|
-
const osRelease = os
|
|
31366
|
+
const osRelease = os.release().split(".");
|
|
30995
31367
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
30996
31368
|
return 1;
|
|
30997
31369
|
}
|
|
@@ -33611,20 +33983,37 @@ function printSubgraphSdl(definition) {
|
|
|
33611
33983
|
//#endregion
|
|
33612
33984
|
//#region src/cli/typescript.ts
|
|
33613
33985
|
/**
|
|
33986
|
+
* Raised when the Asset's `typescript` cannot serve the capability lint or the
|
|
33987
|
+
* type-check: it is not installed, or the module that loads has no compiler
|
|
33988
|
+
* API (`typescript@7` exports `lib/version.cjs` and no `createSourceFile`).
|
|
33989
|
+
* `validateExtension` turns it into `ValidationReport.typescript` instead of
|
|
33990
|
+
* failing the whole run (ADR-0026, TypeScript 5 through the peer).
|
|
33991
|
+
*/
|
|
33992
|
+
var TypeScriptUnavailableError = class extends Error {
|
|
33993
|
+
constructor(message) {
|
|
33994
|
+
super(message);
|
|
33995
|
+
this.name = "TypeScriptUnavailableError";
|
|
33996
|
+
}
|
|
33997
|
+
};
|
|
33998
|
+
/**
|
|
33614
33999
|
* The TypeScript compiler, imported on first use. `typescript` is an optional
|
|
33615
34000
|
* peer of `@vtex/faststore`: every TypeScript extension already has it, and
|
|
33616
34001
|
* only the capability lint and `build`'s type-check need it, so `dist/cli`
|
|
33617
34002
|
* loads without it and a store app installs none. A host that calls those two
|
|
33618
34003
|
* without it installed gets the package named instead of a bare resolution
|
|
33619
|
-
* error.
|
|
34004
|
+
* error. The check is for the compiler API, not the version string, so a
|
|
34005
|
+
* TypeScript 5 fork or a later major that keeps the API still passes.
|
|
33620
34006
|
*/
|
|
33621
34007
|
async function loadTypeScript(verb) {
|
|
34008
|
+
let mod;
|
|
33622
34009
|
try {
|
|
33623
|
-
|
|
33624
|
-
return mod.default ?? mod;
|
|
34010
|
+
mod = await import("typescript");
|
|
33625
34011
|
} catch (error) {
|
|
33626
|
-
throw new
|
|
34012
|
+
throw new TypeScriptUnavailableError(`storefront ${verb}: \`typescript\` is not installed. Add it to the extension's devDependencies. Cause: ${error instanceof Error ? error.message : String(error)}`);
|
|
33627
34013
|
}
|
|
34014
|
+
const ts = mod.default ?? mod;
|
|
34015
|
+
if (typeof ts.createSourceFile !== "function") throw new TypeScriptUnavailableError(`storefront ${verb}: ${typeof ts.version === "string" ? `typescript@${ts.version}` : "`typescript`"} has no TypeScript 5 compiler API (\`createSourceFile\` is not a function).`);
|
|
34016
|
+
return ts;
|
|
33628
34017
|
}
|
|
33629
34018
|
//#endregion
|
|
33630
34019
|
//#region src/cli/capability-lint.ts
|
|
@@ -33768,7 +34157,7 @@ function formatFinding(finding) {
|
|
|
33768
34157
|
* report — the shared core behind the host's `validate` verb and `build`'s
|
|
33769
34158
|
* fail-closed gate. It:
|
|
33770
34159
|
*
|
|
33771
|
-
* 1. loads the declaration through the
|
|
34160
|
+
* 1. loads the declaration through the runner's esbuild (the same
|
|
33772
34161
|
* `loadDeclaration` `dev` uses),
|
|
33773
34162
|
* 2. builds the subgraph (allow-list rejection and `vendor` validation surface
|
|
33774
34163
|
* here as a thrown error, captured into `buildError`),
|
|
@@ -33781,14 +34170,21 @@ function formatFinding(finding) {
|
|
|
33781
34170
|
* composition check is `@theguild/federation-composition` in-process, the same
|
|
33782
34171
|
* composer `dev` and the core SDL CI gate call (ADR-0004, D1).
|
|
33783
34172
|
*/
|
|
33784
|
-
async function validateExtension(extensionRoot
|
|
33785
|
-
|
|
33786
|
-
|
|
34173
|
+
async function validateExtension(extensionRoot) {
|
|
34174
|
+
const entry = resolveDeclarationEntry(extensionRoot);
|
|
34175
|
+
let capability = [];
|
|
34176
|
+
let typescript;
|
|
34177
|
+
try {
|
|
34178
|
+
capability = await lintExtension(entry);
|
|
34179
|
+
} catch (error) {
|
|
34180
|
+
if (!(error instanceof TypeScriptUnavailableError)) throw error;
|
|
34181
|
+
typescript = error;
|
|
34182
|
+
}
|
|
33787
34183
|
let definition;
|
|
33788
34184
|
let buildError;
|
|
33789
34185
|
const composition = [];
|
|
33790
34186
|
try {
|
|
33791
|
-
definition = await loadDeclaration(extensionRoot,
|
|
34187
|
+
definition = await loadDeclaration(extensionRoot, "validate");
|
|
33792
34188
|
buildSubgraph(definition);
|
|
33793
34189
|
} catch (error) {
|
|
33794
34190
|
buildError = error instanceof Error ? error : new Error(String(error));
|
|
@@ -33797,22 +34193,24 @@ async function validateExtension(extensionRoot, host) {
|
|
|
33797
34193
|
const result = (0, import_compose.composeCheck)(coreFederationSdl(), printSubgraphSdl(definition));
|
|
33798
34194
|
if (!result.success) composition.push(...result.errors);
|
|
33799
34195
|
}
|
|
33800
|
-
const ok = capability.length === 0 && composition.length === 0 && buildError === void 0;
|
|
34196
|
+
const ok = typescript === void 0 && capability.length === 0 && composition.length === 0 && buildError === void 0;
|
|
33801
34197
|
return {
|
|
33802
34198
|
capability,
|
|
33803
34199
|
composition,
|
|
33804
34200
|
buildError,
|
|
34201
|
+
typescript,
|
|
33805
34202
|
ok
|
|
33806
34203
|
};
|
|
33807
34204
|
}
|
|
33808
34205
|
/**
|
|
33809
34206
|
* Renders a `ValidationReport`'s failures as human-readable lines, grouped by
|
|
33810
|
-
* category (build/allow-list, capability, composition) — every
|
|
33811
|
-
* pass, never short-circuited at the first. Returns an empty
|
|
33812
|
-
* report so the caller prints its own success line.
|
|
34207
|
+
* category (typescript, build/allow-list, capability, composition) — every
|
|
34208
|
+
* failure in one pass, never short-circuited at the first. Returns an empty
|
|
34209
|
+
* array for a clean report so the caller prints its own success line.
|
|
33813
34210
|
*/
|
|
33814
34211
|
function formatValidationReport(report) {
|
|
33815
34212
|
const lines = [];
|
|
34213
|
+
if (report.typescript) lines.push(` typescript: ${report.typescript.message}`);
|
|
33816
34214
|
if (report.buildError) lines.push(` build: ${report.buildError.message}`);
|
|
33817
34215
|
for (const finding of report.capability) lines.push(` capability: ${formatFinding(finding)}`);
|
|
33818
34216
|
for (const error of report.composition) lines.push(` composition: ${error.message}`);
|
|
@@ -33845,7 +34243,7 @@ var CompileError = class extends Error {
|
|
|
33845
34243
|
*/
|
|
33846
34244
|
function parseTsConfig(ts, extensionRoot) {
|
|
33847
34245
|
const tsconfigPath = join(extensionRoot, "tsconfig.json");
|
|
33848
|
-
if (!existsSync(tsconfigPath)) throw new CompileError(
|
|
34246
|
+
if (!existsSync(tsconfigPath)) throw new CompileError("storefront build: no tsconfig.json found at the Asset root.");
|
|
33849
34247
|
const read = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
33850
34248
|
if (read.error) throw new CompileError(`storefront build: could not read tsconfig.json:\n${ts.formatDiagnostic(read.error, diagnosticHost(ts))}`);
|
|
33851
34249
|
const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, extensionRoot);
|
|
@@ -33871,10 +34269,10 @@ function diagnosticHost(ts) {
|
|
|
33871
34269
|
* `server.mjs` embeds, and a `package.json` `main` outside the `include` would
|
|
33872
34270
|
* otherwise ship unchecked.
|
|
33873
34271
|
*/
|
|
33874
|
-
async function typecheck(extensionRoot) {
|
|
34272
|
+
async function typecheck(extensionRoot, entryPath) {
|
|
33875
34273
|
const ts = await loadTypeScript("build");
|
|
33876
34274
|
const parsed = parseTsConfig(ts, extensionRoot);
|
|
33877
|
-
const rootNames = [.../* @__PURE__ */ new Set([...parsed.fileNames,
|
|
34275
|
+
const rootNames = [.../* @__PURE__ */ new Set([...parsed.fileNames, entryPath])];
|
|
33878
34276
|
const program = ts.createProgram({
|
|
33879
34277
|
rootNames,
|
|
33880
34278
|
options: parsed.options
|
|
@@ -33889,7 +34287,7 @@ async function typecheck(extensionRoot) {
|
|
|
33889
34287
|
*/
|
|
33890
34288
|
function serverRuntimePath() {
|
|
33891
34289
|
const path = fileURLToPath(new URL("./server-runtime.js", import.meta.url));
|
|
33892
|
-
if (!existsSync(path)) throw new Error(
|
|
34290
|
+
if (!existsSync(path)) throw new Error("storefront build: the server runtime (dist/cli/server-runtime.js) is missing beside the ./cli entry — is @vtex/faststore built?");
|
|
33893
34291
|
return path;
|
|
33894
34292
|
}
|
|
33895
34293
|
/**
|
|
@@ -33905,8 +34303,8 @@ function serverRuntimePath() {
|
|
|
33905
34303
|
* bundle carries a banner of the same shape; esbuild renames that module's
|
|
33906
34304
|
* binding, so only the import alias has to differ.
|
|
33907
34305
|
*/
|
|
33908
|
-
async function bundleServer(
|
|
33909
|
-
const bundler =
|
|
34306
|
+
async function bundleServer(extensionRoot, entryPath, name, outfile) {
|
|
34307
|
+
const bundler = await loadBundler("build");
|
|
33910
34308
|
const contents = [
|
|
33911
34309
|
`import definition from ${JSON.stringify(entryPath)};`,
|
|
33912
34310
|
`import { runServer } from ${JSON.stringify(serverRuntimePath())};`,
|
|
@@ -33934,31 +34332,44 @@ async function bundleServer(host, extensionRoot, entryPath, name, outfile) {
|
|
|
33934
34332
|
}
|
|
33935
34333
|
}
|
|
33936
34334
|
/**
|
|
33937
|
-
*
|
|
33938
|
-
*
|
|
33939
|
-
*
|
|
33940
|
-
*
|
|
33941
|
-
*
|
|
33942
|
-
*
|
|
33943
|
-
*
|
|
33944
|
-
|
|
33945
|
-
|
|
33946
|
-
|
|
33947
|
-
|
|
34335
|
+
* What the swap would empty out of the Asset if it emptied `outDir`, or
|
|
34336
|
+
* `undefined` for a folder the build may own: the Asset itself (`outDir` is the
|
|
34337
|
+
* root or an ancestor), the scratch folder that holds the staging folder, the
|
|
34338
|
+
* router cache and the dev lock, or the Asset's source (`src/`, the declaration
|
|
34339
|
+
* entry's folder), whether `outDir` contains the folder or lies inside it.
|
|
34340
|
+
* Compared as the filesystem spells them (`paths.ts`). A leaf of its own
|
|
34341
|
+
* (`<root>/dist`, `<root>/.vtex/build/…`) and a sibling are fine.
|
|
34342
|
+
*/
|
|
34343
|
+
function heldByOutDir(outDir, root, entry) {
|
|
34344
|
+
if (isInside(root, outDir)) return "contains the Asset (it is the root or an ancestor)";
|
|
34345
|
+
const scratch = join(root, SCRATCH_PARENT_DIR_NAME, SCRATCH_DIR_NAME);
|
|
34346
|
+
const scratchName = `the scratch folder ${SCRATCH_PARENT_DIR_NAME}/${SCRATCH_DIR_NAME}/`;
|
|
34347
|
+
if (isInside(scratch, outDir)) return `contains ${scratchName}`;
|
|
34348
|
+
if (isInside(outDir, scratch)) return `lies inside ${scratchName}`;
|
|
34349
|
+
const source = join(root, "src");
|
|
34350
|
+
if (isInside(source, outDir)) return "contains the Asset's source folder src/";
|
|
34351
|
+
if (isInside(outDir, source)) return "lies inside the Asset's source folder src/";
|
|
34352
|
+
if (entry !== void 0) {
|
|
34353
|
+
if (isInside(entry, outDir)) return "contains the declaration entry";
|
|
34354
|
+
const entryDir = dirname(entry);
|
|
34355
|
+
if (!isInside(root, entryDir) && isInside(outDir, entryDir)) return "lies inside the declaration entry's folder";
|
|
34356
|
+
}
|
|
34357
|
+
}
|
|
34358
|
+
/**
|
|
34359
|
+
* The API side's gate and compose, all before a byte of output exists: `validate`
|
|
34360
|
+
* fail-closed (any failure throws `ValidationGateError`), the type-check, the
|
|
34361
|
+
* declaration load through the runner's esbuild, the SDL slice from
|
|
34362
|
+
* `printSubgraphSdl` of that declaration, and its composition with the embedded
|
|
34363
|
+
* core SDL.
|
|
33948
34364
|
*
|
|
33949
|
-
*
|
|
33950
|
-
*
|
|
33951
|
-
* They are composed from the same slice `server.mjs` serves and written only
|
|
33952
|
-
* after `dist/` is in place, so an emitted artifact always describes emitted
|
|
33953
|
-
* output.
|
|
34365
|
+
* esbuild is imported on first use, inside `validate`'s declaration load, so a
|
|
34366
|
+
* broken install surfaces here, before `outDir` or `.vtex/` is written.
|
|
33954
34367
|
*/
|
|
33955
|
-
async function
|
|
33956
|
-
|
|
33957
|
-
const root = resolve(extensionRoot);
|
|
33958
|
-
const report = await validateExtension(root, host);
|
|
34368
|
+
async function prepareApiSide(root, entry) {
|
|
34369
|
+
const report = await validateExtension(root);
|
|
33959
34370
|
if (!report.ok) throw new ValidationGateError(formatValidationReport(report));
|
|
33960
|
-
await typecheck(root);
|
|
33961
|
-
const definition = await loadDeclaration(root,
|
|
34371
|
+
await typecheck(root, entry);
|
|
34372
|
+
const definition = await loadDeclaration(root, "build");
|
|
33962
34373
|
const name = extensionName(root, definition);
|
|
33963
34374
|
const extensionSdl = printSubgraphSdl(definition);
|
|
33964
34375
|
const composed = (0, import_compose.composeSupergraph)([{
|
|
@@ -33971,24 +34382,143 @@ async function buildExtension(extensionRoot, host) {
|
|
|
33971
34382
|
sdl: extensionSdl
|
|
33972
34383
|
}]);
|
|
33973
34384
|
if (!composed.success) throw new Error(formatCompositionErrors("build", composed.errors));
|
|
34385
|
+
return {
|
|
34386
|
+
entry,
|
|
34387
|
+
name,
|
|
34388
|
+
extensionSdl,
|
|
34389
|
+
composed
|
|
34390
|
+
};
|
|
34391
|
+
}
|
|
34392
|
+
/**
|
|
34393
|
+
* Replaces `outDir` with the assembled staging dir in one rename. A rename
|
|
34394
|
+
* across filesystems fails with `EXDEV` (an `--out` on another volume, a tmpfs
|
|
34395
|
+
* `/tmp`); on that code alone the staging dir is copied to a sibling of
|
|
34396
|
+
* `outDir`, on its volume, and that sibling is renamed into place, so a copy
|
|
34397
|
+
* that stops halfway (a full disk) leaves `outDir` absent instead of partial.
|
|
34398
|
+
* The caller's `finally` removes the staging dir. Anything else propagates.
|
|
34399
|
+
*/
|
|
34400
|
+
function swapInto(stagingDir, outDir) {
|
|
34401
|
+
rmSync(outDir, {
|
|
34402
|
+
recursive: true,
|
|
34403
|
+
force: true
|
|
34404
|
+
});
|
|
34405
|
+
mkdirSync(dirname(outDir), { recursive: true });
|
|
34406
|
+
try {
|
|
34407
|
+
renameSync(stagingDir, outDir);
|
|
34408
|
+
} catch (error) {
|
|
34409
|
+
if (!(error instanceof Error && "code" in error && error.code === "EXDEV")) throw error;
|
|
34410
|
+
const landing = `${outDir}.partial-${pid}`;
|
|
34411
|
+
try {
|
|
34412
|
+
cpSync(stagingDir, landing, { recursive: true });
|
|
34413
|
+
renameSync(landing, outDir);
|
|
34414
|
+
} finally {
|
|
34415
|
+
rmSync(landing, {
|
|
34416
|
+
recursive: true,
|
|
34417
|
+
force: true
|
|
34418
|
+
});
|
|
34419
|
+
}
|
|
34420
|
+
}
|
|
34421
|
+
}
|
|
34422
|
+
/** The UI side as `BuildResult` reports it: the addon's staging paths rebased onto `outDir`. */
|
|
34423
|
+
function uiBuildOutput(staged, stagingDir, outDir, docs) {
|
|
34424
|
+
const emitted = (path) => join(outDir, relative(stagingDir, path));
|
|
34425
|
+
return {
|
|
34426
|
+
catalogPath: emitted(staged.catalogPath),
|
|
34427
|
+
contentTypesPath: staged.contentTypesPath === null ? null : emitted(staged.contentTypesPath),
|
|
34428
|
+
docsDir: join(outDir, "docs"),
|
|
34429
|
+
catalogEntries: staged.catalogEntries,
|
|
34430
|
+
docs
|
|
34431
|
+
};
|
|
34432
|
+
}
|
|
34433
|
+
/**
|
|
34434
|
+
* Copies the catalog, and the content types when there are any, from `outDir`
|
|
34435
|
+
* to the folder the Asset's `exports["./catalog.json"]` names: that export is
|
|
34436
|
+
* how a store finds the Asset (ADR-0007), so a store builds against a built
|
|
34437
|
+
* Asset with no `dev` loop having run. Nothing to copy when `outDir` already
|
|
34438
|
+
* is that folder.
|
|
34439
|
+
*/
|
|
34440
|
+
function copyCatalogToExport(ui, exported, outDir) {
|
|
34441
|
+
if (canonical(exported) === canonical(outDir)) return;
|
|
34442
|
+
mkdirSync(exported, { recursive: true });
|
|
34443
|
+
copyFileSync(ui.catalogPath, join(exported, "catalog.json"));
|
|
34444
|
+
if (ui.contentTypesPath !== null) copyFileSync(ui.contentTypesPath, join(exported, "content-types.json"));
|
|
34445
|
+
}
|
|
34446
|
+
/**
|
|
34447
|
+
* Produces the deployable output for a storefront Asset, both sides into the
|
|
34448
|
+
* folder the caller names: `server.mjs` and the SDL slice for the API side,
|
|
34449
|
+
* `catalog.json` (and `content-types.json` when the addon emitted one) with
|
|
34450
|
+
* the component docs flat under `docs/` for the UI side (ADR-0026). `outDir`
|
|
34451
|
+
* is `dist` under the root unless passed; `vtex extension build` passes the
|
|
34452
|
+
* folder it chose, `.vtex/build/<ext>/<asset>/` by default. An `outDir` equal
|
|
34453
|
+
* to the root or containing it is refused before anything runs, since the swap
|
|
34454
|
+
* below empties it. An Asset with neither side throws: there is nothing to
|
|
34455
|
+
* build.
|
|
34456
|
+
*
|
|
34457
|
+
* The UI side's `catalog.json` (and `content-types.json`) also land in the
|
|
34458
|
+
* folder the Asset's `exports["./catalog.json"]` names, `dist/` by the
|
|
34459
|
+
* template: that export is how a store finds the Asset (ADR-0007), so a store
|
|
34460
|
+
* builds against a built Asset with no `dev` loop having run. Nothing is
|
|
34461
|
+
* copied when `outDir` already is that folder.
|
|
34462
|
+
*
|
|
34463
|
+
* Everything that can fail runs before a byte of output exists: the API side's
|
|
34464
|
+
* `validate` gate, type-check, declaration load and compose; the UI side's
|
|
34465
|
+
* addon load and docs plan (a basename collision throws here). Both sides then
|
|
34466
|
+
* write into one staging dir under the `.vtex/storefront/` scratch dir — the
|
|
34467
|
+
* SDL and the bundled server, the catalog built with `kind: 'extension'` (an
|
|
34468
|
+
* Asset named `@vtex/faststore` fails as the Core package it is not) and the
|
|
34469
|
+
* docs copy — and only then is the staging dir swapped into `outDir`. `outDir`
|
|
34470
|
+
* is therefore only ever absent, the previous complete output, or the new
|
|
34471
|
+
* complete output — a failed build never leaves partial output or keeps stale
|
|
34472
|
+
* files from an earlier build. The UI side emits no JavaScript: the store
|
|
34473
|
+
* compiles the components it imports. No `router.json`, and no composed
|
|
34474
|
+
* artifact (Decision 1). Throws on any failure so the caller can surface it
|
|
34475
|
+
* and exit non-zero.
|
|
34476
|
+
*
|
|
34477
|
+
* The composed artifacts go to `.vtex/storefront/` scratch instead, so a
|
|
34478
|
+
* store's CI compile has a fresh client schema without a running `dev` loop.
|
|
34479
|
+
* They are composed from the same slice `server.mjs` serves and written only
|
|
34480
|
+
* after `outDir` is in place, so an emitted artifact always describes emitted
|
|
34481
|
+
* output. A UI-only Asset composes nothing.
|
|
34482
|
+
*/
|
|
34483
|
+
async function buildExtension(extensionRoot, { log = SILENT_LOG, outDir: outDirOption } = {}) {
|
|
34484
|
+
const root = resolve(extensionRoot);
|
|
34485
|
+
const outDir = resolve(root, outDirOption ?? "dist");
|
|
34486
|
+
const layout = readAssetLayout(root);
|
|
34487
|
+
const entry = apiDeclarationEntry(layout);
|
|
34488
|
+
const held = heldByOutDir(outDir, root, entry);
|
|
34489
|
+
if (held !== void 0) throw new Error(`storefront build: outDir ${JSON.stringify(outDirOption ?? "dist")} ${held} — the build empties outDir.`);
|
|
34490
|
+
const ui = uiSide(layout);
|
|
34491
|
+
if (entry === void 0 && !ui) throw new Error("storefront build: the Asset has no API side (no package.json#main and no src/index.ts) and no UI side (no src/components/ and no exports[\"./catalog.json\"]) — nothing to build.");
|
|
34492
|
+
const apiSide = entry === void 0 ? void 0 : await prepareApiSide(root, entry);
|
|
34493
|
+
if (ui) await loadCatalogBuilder("build");
|
|
34494
|
+
const docCopies = ui ? planDocCopies(root) : [];
|
|
33974
34495
|
const stagingDir = mkdtempSync(join(scratchDir(root), "build-"));
|
|
33975
34496
|
try {
|
|
33976
|
-
|
|
33977
|
-
|
|
33978
|
-
|
|
33979
|
-
|
|
33980
|
-
|
|
33981
|
-
|
|
33982
|
-
|
|
33983
|
-
|
|
33984
|
-
|
|
33985
|
-
|
|
33986
|
-
|
|
33987
|
-
|
|
33988
|
-
|
|
33989
|
-
|
|
33990
|
-
|
|
33991
|
-
|
|
34497
|
+
if (apiSide) {
|
|
34498
|
+
writeFileSync(join(stagingDir, "extension.graphql"), apiSide.extensionSdl, "utf-8");
|
|
34499
|
+
await bundleServer(root, apiSide.entry, apiSide.name, join(stagingDir, "server.mjs"));
|
|
34500
|
+
}
|
|
34501
|
+
let uiStaged;
|
|
34502
|
+
if (ui) {
|
|
34503
|
+
uiStaged = await buildAssetCatalog(root, stagingDir, "build");
|
|
34504
|
+
copyDocs(docCopies, join(stagingDir, "docs"));
|
|
34505
|
+
}
|
|
34506
|
+
swapInto(stagingDir, outDir);
|
|
34507
|
+
const result = { outDir };
|
|
34508
|
+
if (apiSide) {
|
|
34509
|
+
const { supergraphFile, publicSchemaFile } = writeComposedArtifacts(root, apiSide.composed, "build", log);
|
|
34510
|
+
result.api = {
|
|
34511
|
+
serverFile: join(outDir, "server.mjs"),
|
|
34512
|
+
sdlPath: join(outDir, "extension.graphql"),
|
|
34513
|
+
supergraphFile,
|
|
34514
|
+
publicSchemaFile
|
|
34515
|
+
};
|
|
34516
|
+
}
|
|
34517
|
+
if (uiStaged) {
|
|
34518
|
+
result.ui = uiBuildOutput(uiStaged, stagingDir, outDir, docCopies.length);
|
|
34519
|
+
copyCatalogToExport(result.ui, catalogOutDir(layout), outDir);
|
|
34520
|
+
}
|
|
34521
|
+
return result;
|
|
33992
34522
|
} finally {
|
|
33993
34523
|
rmSync(stagingDir, {
|
|
33994
34524
|
recursive: true,
|
|
@@ -33998,282 +34528,10 @@ async function buildExtension(extensionRoot, host) {
|
|
|
33998
34528
|
}
|
|
33999
34529
|
//#endregion
|
|
34000
34530
|
//#region src/cli/router-config.ts
|
|
34001
|
-
var import_main = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
34002
|
-
const fs = __require("fs");
|
|
34003
|
-
const path = __require("path");
|
|
34004
|
-
const os = __require("os");
|
|
34005
|
-
const crypto$1 = __require("crypto");
|
|
34006
|
-
const TIPS = [
|
|
34007
|
-
"◈ encrypted .env [www.dotenvx.com]",
|
|
34008
|
-
"◈ secrets for agents [www.dotenvx.com]",
|
|
34009
|
-
"⌁ auth for agents [www.vestauth.com]",
|
|
34010
|
-
"⌘ custom filepath { path: '/custom/path/.env' }",
|
|
34011
|
-
"⌘ enable debugging { debug: true }",
|
|
34012
|
-
"⌘ override existing { override: true }",
|
|
34013
|
-
"⌘ suppress logs { quiet: true }",
|
|
34014
|
-
"⌘ multiple files { path: ['.env.local', '.env'] }"
|
|
34015
|
-
];
|
|
34016
|
-
function _getRandomTip() {
|
|
34017
|
-
return TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
34018
|
-
}
|
|
34019
|
-
function parseBoolean(value) {
|
|
34020
|
-
if (typeof value === "string") return ![
|
|
34021
|
-
"false",
|
|
34022
|
-
"0",
|
|
34023
|
-
"no",
|
|
34024
|
-
"off",
|
|
34025
|
-
""
|
|
34026
|
-
].includes(value.toLowerCase());
|
|
34027
|
-
return Boolean(value);
|
|
34028
|
-
}
|
|
34029
|
-
function supportsAnsi() {
|
|
34030
|
-
return process.stdout.isTTY;
|
|
34031
|
-
}
|
|
34032
|
-
function dim(text) {
|
|
34033
|
-
return supportsAnsi() ? `\x1b[2m${text}\x1b[0m` : text;
|
|
34034
|
-
}
|
|
34035
|
-
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
|
34036
|
-
function parse(src) {
|
|
34037
|
-
const obj = {};
|
|
34038
|
-
let lines = src.toString();
|
|
34039
|
-
lines = lines.replace(/\r\n?/gm, "\n");
|
|
34040
|
-
let match;
|
|
34041
|
-
while ((match = LINE.exec(lines)) != null) {
|
|
34042
|
-
const key = match[1];
|
|
34043
|
-
let value = match[2] || "";
|
|
34044
|
-
value = value.trim();
|
|
34045
|
-
const maybeQuote = value[0];
|
|
34046
|
-
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
|
34047
|
-
if (maybeQuote === "\"") {
|
|
34048
|
-
value = value.replace(/\\n/g, "\n");
|
|
34049
|
-
value = value.replace(/\\r/g, "\r");
|
|
34050
|
-
}
|
|
34051
|
-
obj[key] = value;
|
|
34052
|
-
}
|
|
34053
|
-
return obj;
|
|
34054
|
-
}
|
|
34055
|
-
function _parseVault(options) {
|
|
34056
|
-
options = options || {};
|
|
34057
|
-
const vaultPath = _vaultPath(options);
|
|
34058
|
-
options.path = vaultPath;
|
|
34059
|
-
const result = DotenvModule.configDotenv(options);
|
|
34060
|
-
if (!result.parsed) {
|
|
34061
|
-
const err = /* @__PURE__ */ new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
34062
|
-
err.code = "MISSING_DATA";
|
|
34063
|
-
throw err;
|
|
34064
|
-
}
|
|
34065
|
-
const keys = _dotenvKey(options).split(",");
|
|
34066
|
-
const length = keys.length;
|
|
34067
|
-
let decrypted;
|
|
34068
|
-
for (let i = 0; i < length; i++) try {
|
|
34069
|
-
const attrs = _instructions(result, keys[i].trim());
|
|
34070
|
-
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
34071
|
-
break;
|
|
34072
|
-
} catch (error) {
|
|
34073
|
-
if (i + 1 >= length) throw error;
|
|
34074
|
-
}
|
|
34075
|
-
return DotenvModule.parse(decrypted);
|
|
34076
|
-
}
|
|
34077
|
-
function _warn(message) {
|
|
34078
|
-
console.error(`⚠ ${message}`);
|
|
34079
|
-
}
|
|
34080
|
-
function _debug(message) {
|
|
34081
|
-
console.log(`┆ ${message}`);
|
|
34082
|
-
}
|
|
34083
|
-
function _log(message) {
|
|
34084
|
-
console.log(`◇ ${message}`);
|
|
34085
|
-
}
|
|
34086
|
-
function _dotenvKey(options) {
|
|
34087
|
-
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) return options.DOTENV_KEY;
|
|
34088
|
-
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) return process.env.DOTENV_KEY;
|
|
34089
|
-
return "";
|
|
34090
|
-
}
|
|
34091
|
-
function _instructions(result, dotenvKey) {
|
|
34092
|
-
let uri;
|
|
34093
|
-
try {
|
|
34094
|
-
uri = new URL(dotenvKey);
|
|
34095
|
-
} catch (error) {
|
|
34096
|
-
if (error.code === "ERR_INVALID_URL") {
|
|
34097
|
-
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
|
|
34098
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
34099
|
-
throw err;
|
|
34100
|
-
}
|
|
34101
|
-
throw error;
|
|
34102
|
-
}
|
|
34103
|
-
const key = uri.password;
|
|
34104
|
-
if (!key) {
|
|
34105
|
-
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Missing key part");
|
|
34106
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
34107
|
-
throw err;
|
|
34108
|
-
}
|
|
34109
|
-
const environment = uri.searchParams.get("environment");
|
|
34110
|
-
if (!environment) {
|
|
34111
|
-
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Missing environment part");
|
|
34112
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
34113
|
-
throw err;
|
|
34114
|
-
}
|
|
34115
|
-
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
34116
|
-
const ciphertext = result.parsed[environmentKey];
|
|
34117
|
-
if (!ciphertext) {
|
|
34118
|
-
const err = /* @__PURE__ */ new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
34119
|
-
err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
|
|
34120
|
-
throw err;
|
|
34121
|
-
}
|
|
34122
|
-
return {
|
|
34123
|
-
ciphertext,
|
|
34124
|
-
key
|
|
34125
|
-
};
|
|
34126
|
-
}
|
|
34127
|
-
function _vaultPath(options) {
|
|
34128
|
-
let possibleVaultPath = null;
|
|
34129
|
-
if (options && options.path && options.path.length > 0) {
|
|
34130
|
-
if (Array.isArray(options.path)) {
|
|
34131
|
-
for (const filepath of options.path) if (fs.existsSync(filepath)) possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
34132
|
-
} else possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
|
|
34133
|
-
} else possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
|
|
34134
|
-
if (fs.existsSync(possibleVaultPath)) return possibleVaultPath;
|
|
34135
|
-
return null;
|
|
34136
|
-
}
|
|
34137
|
-
function _resolveHome(envPath) {
|
|
34138
|
-
return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
|
|
34139
|
-
}
|
|
34140
|
-
function _configVault(options) {
|
|
34141
|
-
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
34142
|
-
const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
34143
|
-
if (debug || !quiet) _log("loading env from encrypted .env.vault");
|
|
34144
|
-
const parsed = DotenvModule._parseVault(options);
|
|
34145
|
-
let processEnv = process.env;
|
|
34146
|
-
if (options && options.processEnv != null) processEnv = options.processEnv;
|
|
34147
|
-
DotenvModule.populate(processEnv, parsed, options);
|
|
34148
|
-
return { parsed };
|
|
34149
|
-
}
|
|
34150
|
-
function configDotenv(options) {
|
|
34151
|
-
const dotenvPath = path.resolve(process.cwd(), ".env");
|
|
34152
|
-
let encoding = "utf8";
|
|
34153
|
-
let processEnv = process.env;
|
|
34154
|
-
if (options && options.processEnv != null) processEnv = options.processEnv;
|
|
34155
|
-
let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
34156
|
-
let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
34157
|
-
if (options && options.encoding) encoding = options.encoding;
|
|
34158
|
-
else if (debug) _debug("no encoding is specified (UTF-8 is used by default)");
|
|
34159
|
-
let optionPaths = [dotenvPath];
|
|
34160
|
-
if (options && options.path) {
|
|
34161
|
-
if (!Array.isArray(options.path)) optionPaths = [_resolveHome(options.path)];
|
|
34162
|
-
else {
|
|
34163
|
-
optionPaths = [];
|
|
34164
|
-
for (const filepath of options.path) optionPaths.push(_resolveHome(filepath));
|
|
34165
|
-
}
|
|
34166
|
-
}
|
|
34167
|
-
let lastError;
|
|
34168
|
-
const parsedAll = {};
|
|
34169
|
-
for (const path of optionPaths) try {
|
|
34170
|
-
const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }));
|
|
34171
|
-
DotenvModule.populate(parsedAll, parsed, options);
|
|
34172
|
-
} catch (e) {
|
|
34173
|
-
if (debug) _debug(`failed to load ${path} ${e.message}`);
|
|
34174
|
-
lastError = e;
|
|
34175
|
-
}
|
|
34176
|
-
const populated = DotenvModule.populate(processEnv, parsedAll, options);
|
|
34177
|
-
debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
|
|
34178
|
-
quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
|
|
34179
|
-
if (debug || !quiet) {
|
|
34180
|
-
const keysCount = Object.keys(populated).length;
|
|
34181
|
-
const shortPaths = [];
|
|
34182
|
-
for (const filePath of optionPaths) try {
|
|
34183
|
-
const relative = path.relative(process.cwd(), filePath);
|
|
34184
|
-
shortPaths.push(relative);
|
|
34185
|
-
} catch (e) {
|
|
34186
|
-
if (debug) _debug(`failed to load ${filePath} ${e.message}`);
|
|
34187
|
-
lastError = e;
|
|
34188
|
-
}
|
|
34189
|
-
_log(`injected env (${keysCount}) from ${shortPaths.join(",")} ${dim(`// tip: ${_getRandomTip()}`)}`);
|
|
34190
|
-
}
|
|
34191
|
-
if (lastError) return {
|
|
34192
|
-
parsed: parsedAll,
|
|
34193
|
-
error: lastError
|
|
34194
|
-
};
|
|
34195
|
-
else return { parsed: parsedAll };
|
|
34196
|
-
}
|
|
34197
|
-
function config(options) {
|
|
34198
|
-
if (_dotenvKey(options).length === 0) return DotenvModule.configDotenv(options);
|
|
34199
|
-
const vaultPath = _vaultPath(options);
|
|
34200
|
-
if (!vaultPath) {
|
|
34201
|
-
_warn(`you set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}`);
|
|
34202
|
-
return DotenvModule.configDotenv(options);
|
|
34203
|
-
}
|
|
34204
|
-
return DotenvModule._configVault(options);
|
|
34205
|
-
}
|
|
34206
|
-
function decrypt(encrypted, keyStr) {
|
|
34207
|
-
const key = Buffer.from(keyStr.slice(-64), "hex");
|
|
34208
|
-
let ciphertext = Buffer.from(encrypted, "base64");
|
|
34209
|
-
const nonce = ciphertext.subarray(0, 12);
|
|
34210
|
-
const authTag = ciphertext.subarray(-16);
|
|
34211
|
-
ciphertext = ciphertext.subarray(12, -16);
|
|
34212
|
-
try {
|
|
34213
|
-
const aesgcm = crypto$1.createDecipheriv("aes-256-gcm", key, nonce);
|
|
34214
|
-
aesgcm.setAuthTag(authTag);
|
|
34215
|
-
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
34216
|
-
} catch (error) {
|
|
34217
|
-
const isRange = error instanceof RangeError;
|
|
34218
|
-
const invalidKeyLength = error.message === "Invalid key length";
|
|
34219
|
-
const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
|
|
34220
|
-
if (isRange || invalidKeyLength) {
|
|
34221
|
-
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
|
|
34222
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
34223
|
-
throw err;
|
|
34224
|
-
} else if (decryptionFailed) {
|
|
34225
|
-
const err = /* @__PURE__ */ new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
|
|
34226
|
-
err.code = "DECRYPTION_FAILED";
|
|
34227
|
-
throw err;
|
|
34228
|
-
} else throw error;
|
|
34229
|
-
}
|
|
34230
|
-
}
|
|
34231
|
-
function populate(processEnv, parsed, options = {}) {
|
|
34232
|
-
const debug = Boolean(options && options.debug);
|
|
34233
|
-
const override = Boolean(options && options.override);
|
|
34234
|
-
const populated = {};
|
|
34235
|
-
if (typeof parsed !== "object") {
|
|
34236
|
-
const err = /* @__PURE__ */ new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
|
|
34237
|
-
err.code = "OBJECT_REQUIRED";
|
|
34238
|
-
throw err;
|
|
34239
|
-
}
|
|
34240
|
-
for (const key of Object.keys(parsed)) if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
34241
|
-
if (override === true) {
|
|
34242
|
-
processEnv[key] = parsed[key];
|
|
34243
|
-
populated[key] = parsed[key];
|
|
34244
|
-
}
|
|
34245
|
-
if (debug) {
|
|
34246
|
-
if (override === true) _debug(`"${key}" is already defined and WAS overwritten`);
|
|
34247
|
-
else _debug(`"${key}" is already defined and was NOT overwritten`);
|
|
34248
|
-
}
|
|
34249
|
-
} else {
|
|
34250
|
-
processEnv[key] = parsed[key];
|
|
34251
|
-
populated[key] = parsed[key];
|
|
34252
|
-
}
|
|
34253
|
-
return populated;
|
|
34254
|
-
}
|
|
34255
|
-
const DotenvModule = {
|
|
34256
|
-
configDotenv,
|
|
34257
|
-
_configVault,
|
|
34258
|
-
_parseVault,
|
|
34259
|
-
config,
|
|
34260
|
-
decrypt,
|
|
34261
|
-
parse,
|
|
34262
|
-
populate
|
|
34263
|
-
};
|
|
34264
|
-
module.exports.configDotenv = DotenvModule.configDotenv;
|
|
34265
|
-
module.exports._configVault = DotenvModule._configVault;
|
|
34266
|
-
module.exports._parseVault = DotenvModule._parseVault;
|
|
34267
|
-
module.exports.config = DotenvModule.config;
|
|
34268
|
-
module.exports.decrypt = DotenvModule.decrypt;
|
|
34269
|
-
module.exports.parse = DotenvModule.parse;
|
|
34270
|
-
module.exports.populate = DotenvModule.populate;
|
|
34271
|
-
module.exports = DotenvModule;
|
|
34272
|
-
})))(), 1);
|
|
34273
34531
|
/**
|
|
34274
34532
|
* The generated router config, as data: `propagate`-all plus a per-subgraph
|
|
34275
|
-
* `insert` of the canonical tenant header pair toward
|
|
34276
|
-
* (not just `propagate` — AC 4/US-3). `authorization` is propagated at
|
|
34533
|
+
* `insert` of the canonical tenant header pair toward every local extension
|
|
34534
|
+
* subgraph (not just `propagate` — AC 4/US-3). `authorization` is propagated at
|
|
34277
34535
|
* request time, with the `.env` `VTEX_TOKEN` as the propagation `default` so
|
|
34278
34536
|
* inbound requests lacking one still reach core authenticated; an inbound
|
|
34279
34537
|
* `authorization` always wins over the default.
|
|
@@ -34287,10 +34545,10 @@ var import_main = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expor
|
|
|
34287
34545
|
* `traffic_shaping.subgraphs.<core>.tls.insecure_skip_ca_verification: true` —
|
|
34288
34546
|
* the escape hatch for pointing `STOREFRONT_CORE_URL` at an internal host whose
|
|
34289
34547
|
* TLS cert doesn't match its name. Scoped to core only; the extension
|
|
34290
|
-
*
|
|
34548
|
+
* subgraphs are plain HTTP on localhost. This runner only runs on a
|
|
34291
34549
|
* developer's machine, so the flag can never affect the deployed router.
|
|
34292
34550
|
*/
|
|
34293
|
-
function buildRouterConfig(env,
|
|
34551
|
+
function buildRouterConfig(env, extensionSubgraphNames) {
|
|
34294
34552
|
const allRules = [
|
|
34295
34553
|
env.token ? {
|
|
34296
34554
|
op: "propagate",
|
|
@@ -34338,10 +34596,10 @@ function buildRouterConfig(env, extensionSubgraphName) {
|
|
|
34338
34596
|
const subgraphRules = [{
|
|
34339
34597
|
subgraphName: import_subgraph_names.CORE_SUBGRAPH_NAME,
|
|
34340
34598
|
rules: coreRules
|
|
34341
|
-
}, {
|
|
34342
|
-
subgraphName
|
|
34599
|
+
}, ...extensionSubgraphNames.map((subgraphName) => ({
|
|
34600
|
+
subgraphName,
|
|
34343
34601
|
rules: tenantRules
|
|
34344
|
-
}];
|
|
34602
|
+
}))];
|
|
34345
34603
|
const subgraphTlsClients = env.insecureSkipTlsVerify ? [{
|
|
34346
34604
|
subgraphName: import_subgraph_names.CORE_SUBGRAPH_NAME,
|
|
34347
34605
|
insecureSkipCaVerification: true
|
|
@@ -34440,14 +34698,14 @@ const RENAMED_VARIABLES = {
|
|
|
34440
34698
|
FS_GRAPHQL_ROUTER_VERSION: "VTEX_STOREFRONT_ROUTER_VERSION",
|
|
34441
34699
|
FS_GRAPHQL_INSECURE_SKIP_TLS_VERIFY: "VTEX_STOREFRONT_INSECURE_SKIP_TLS_VERIFY"
|
|
34442
34700
|
};
|
|
34443
|
-
|
|
34444
|
-
|
|
34445
|
-
|
|
34701
|
+
/** One warning line per renamed variable that is still set, naming the name that is read instead. */
|
|
34702
|
+
function renamedVariableWarnings(env) {
|
|
34703
|
+
const lines = [];
|
|
34446
34704
|
for (const [old, current] of Object.entries(RENAMED_VARIABLES)) {
|
|
34447
|
-
if (env[old] === void 0
|
|
34448
|
-
|
|
34449
|
-
console.warn(`storefront dev: ${old} is no longer read and was ignored — set ${current} instead.`);
|
|
34705
|
+
if (env[old] === void 0) continue;
|
|
34706
|
+
lines.push(`storefront dev: ${old} is no longer read and was ignored — set ${current} instead.`);
|
|
34450
34707
|
}
|
|
34708
|
+
return lines;
|
|
34451
34709
|
}
|
|
34452
34710
|
function parsePort(value, fallback, name) {
|
|
34453
34711
|
if (!value) return fallback;
|
|
@@ -34461,13 +34719,12 @@ function parseBoolFlag(value) {
|
|
|
34461
34719
|
return normalized === "true" || normalized === "1";
|
|
34462
34720
|
}
|
|
34463
34721
|
/**
|
|
34464
|
-
* Reads the runner's env contract from
|
|
34465
|
-
*
|
|
34466
|
-
* `STOREFRONT_CORE_URL` is set — the runner cannot derive a
|
|
34467
|
-
* without one of the two.
|
|
34722
|
+
* Reads the runner's env contract from the map the host passes — the runner
|
|
34723
|
+
* reads no `.env` and no `process.env` itself (ADR-0026). Throws when neither
|
|
34724
|
+
* `VTEX_ACCOUNT` nor `STOREFRONT_CORE_URL` is set — the runner cannot derive a
|
|
34725
|
+
* core routing URL without one of the two.
|
|
34468
34726
|
*/
|
|
34469
|
-
function loadRunnerEnv(env
|
|
34470
|
-
warnOnRenamedVariables(env);
|
|
34727
|
+
function loadRunnerEnv(env) {
|
|
34471
34728
|
const account = env.VTEX_ACCOUNT?.trim() || void 0;
|
|
34472
34729
|
const coreUrl = env.STOREFRONT_CORE_URL?.trim() || void 0;
|
|
34473
34730
|
if (!account && !coreUrl) throw new Error("storefront dev: missing VTEX_ACCOUNT. Set it in .env (or set STOREFRONT_CORE_URL to override the derived core routing URL).");
|
|
@@ -34478,7 +34735,10 @@ function loadRunnerEnv(env = process.env) {
|
|
|
34478
34735
|
coreUrl,
|
|
34479
34736
|
subgraphPort: parsePort(env.VTEX_STOREFRONT_SUBGRAPH_PORT, DEFAULT_SUBGRAPH_PORT, "VTEX_STOREFRONT_SUBGRAPH_PORT"),
|
|
34480
34737
|
routerPort: parsePort(env.VTEX_STOREFRONT_ROUTER_PORT, DEFAULT_ROUTER_PORT, "VTEX_STOREFRONT_ROUTER_PORT"),
|
|
34481
|
-
insecureSkipTlsVerify: parseBoolFlag(env.VTEX_STOREFRONT_INSECURE_SKIP_TLS_VERIFY)
|
|
34738
|
+
insecureSkipTlsVerify: parseBoolFlag(env.VTEX_STOREFRONT_INSECURE_SKIP_TLS_VERIFY),
|
|
34739
|
+
routerVersion: env.VTEX_STOREFRONT_ROUTER_VERSION?.trim() || "0.0.85",
|
|
34740
|
+
routerBinary: env.VTEX_STOREFRONT_ROUTER_BINARY?.trim() || void 0,
|
|
34741
|
+
variables: { ...env }
|
|
34482
34742
|
};
|
|
34483
34743
|
}
|
|
34484
34744
|
/** The deployed-core routing URL: the `STOREFRONT_CORE_URL` override, or account-derived. */
|
|
@@ -34489,18 +34749,20 @@ function resolveCoreUrl(env) {
|
|
|
34489
34749
|
//#endregion
|
|
34490
34750
|
//#region src/cli/secrets.ts
|
|
34491
34751
|
/**
|
|
34492
|
-
* Declared-secret handling
|
|
34493
|
-
*
|
|
34494
|
-
*
|
|
34495
|
-
*
|
|
34496
|
-
*
|
|
34752
|
+
* Declared-secret handling for the two verbs that run resolvers. `dev` only
|
|
34753
|
+
* captures, from the environment the host passed; the scrub is `server.mjs`'s
|
|
34754
|
+
* alone, because the runner never touches `process.env` (ADR-0026). Kept in its
|
|
34755
|
+
* own dependency-free module because the scrub used to live in
|
|
34756
|
+
* `capability-lint.ts`, which imports the TypeScript compiler at module load —
|
|
34757
|
+
* reusing it from `serve` would have put `typescript` on the production runtime
|
|
34758
|
+
* path (see `docs/storefront-api-core/reference-extension-sdk.md`, "Running the artifact: serve").
|
|
34497
34759
|
*/
|
|
34498
34760
|
/**
|
|
34499
|
-
* Captures the declared secrets' values from an environment snapshot.
|
|
34500
|
-
*
|
|
34501
|
-
* `
|
|
34502
|
-
*
|
|
34503
|
-
* (never `''`) — absent means absent (ADR-0003).
|
|
34761
|
+
* Captures the declared secrets' values from an environment snapshot. `dev`
|
|
34762
|
+
* passes its boot snapshot (`RunnerEnv.variables`) on boot and on every watch
|
|
34763
|
+
* reload, so `ctx.secrets` never empties mid-session; `serve` passes the map it
|
|
34764
|
+
* scrubs afterwards. A declared-but-unset name is simply absent from the
|
|
34765
|
+
* returned map (never `''`) — absent means absent (ADR-0003).
|
|
34504
34766
|
*/
|
|
34505
34767
|
function captureSecrets(secretNames, source) {
|
|
34506
34768
|
const captured = {};
|
|
@@ -34511,215 +34773,175 @@ function captureSecrets(secretNames, source) {
|
|
|
34511
34773
|
return captured;
|
|
34512
34774
|
}
|
|
34513
34775
|
/**
|
|
34514
|
-
*
|
|
34776
|
+
* One loud, non-blocking warning line per declared secret missing from the
|
|
34515
34777
|
* environment. Lives here, not in `buildContext`, so the factory stays pure.
|
|
34516
34778
|
* The server still starts; the secret is just absent from `ctx.secrets`.
|
|
34517
34779
|
*/
|
|
34518
|
-
function
|
|
34519
|
-
|
|
34520
|
-
}
|
|
34521
|
-
|
|
34522
|
-
* Defense in depth: removes the declared secret names from the environment once
|
|
34523
|
-
* their values are captured, so no resolver can read them off `process.env` even
|
|
34524
|
-
* if the capability lint was bypassed.
|
|
34525
|
-
*/
|
|
34526
|
-
function scrubDeclaredSecrets(secretNames, env = process.env) {
|
|
34527
|
-
for (const name of secretNames) delete env[name];
|
|
34780
|
+
function unsetSecretWarnings(secretNames, secrets, verb) {
|
|
34781
|
+
const lines = [];
|
|
34782
|
+
for (const name of secretNames) if (secrets[name] === void 0) lines.push(`storefront ${verb}: ⚠ declared secret ${name} is unset in the environment. ctx.secrets.${name} will be undefined. Set it in the environment before shipping.`);
|
|
34783
|
+
return lines;
|
|
34528
34784
|
}
|
|
34529
34785
|
//#endregion
|
|
34530
|
-
//#region src/cli/dev.ts
|
|
34786
|
+
//#region src/cli/dev-session.ts
|
|
34531
34787
|
/**
|
|
34532
|
-
* Loads the
|
|
34533
|
-
*
|
|
34534
|
-
* the
|
|
34535
|
-
*
|
|
34536
|
-
* inherited shell value.
|
|
34537
|
-
*/
|
|
34538
|
-
function loadExtensionEnv(extensionRoot) {
|
|
34539
|
-
const result = import_main.default.config({
|
|
34540
|
-
path: join(extensionRoot, ".env"),
|
|
34541
|
-
override: true
|
|
34542
|
-
});
|
|
34543
|
-
return loadRunnerEnv({
|
|
34544
|
-
...process.env,
|
|
34545
|
-
...result.parsed
|
|
34546
|
-
});
|
|
34547
|
-
}
|
|
34548
|
-
/**
|
|
34549
|
-
* Loud, non-blocking capability-lint feedback at dev boot. Prints each finding
|
|
34550
|
-
* but never fails the loop — `dev` stays iterable mid-fix; the blocking gate is
|
|
34551
|
-
* `lintExtension` (and the future publish flow). See ADR-0002.
|
|
34552
|
-
*/
|
|
34553
|
-
async function warnOnCapabilityFindings(extensionRoot) {
|
|
34554
|
-
const findings = await lintExtension(resolveDeclarationEntry(extensionRoot));
|
|
34555
|
-
for (const finding of findings) console.warn(`storefront dev: ⚠ capability lint — ${formatFinding(finding)}. This will FAIL publish; fix before shipping.`);
|
|
34556
|
-
}
|
|
34557
|
-
/**
|
|
34558
|
-
* Loads the declaration, starts its subgraph server, composes the embedded core
|
|
34559
|
-
* SDL with the emitted extension slice, and writes the composed artifacts
|
|
34560
|
-
* (`supergraph.graphql` + `public.graphql`) at their fixed scratch paths.
|
|
34561
|
-
* Isolated as one step because watch mode re-runs exactly this on save —
|
|
34562
|
-
* restart the subgraph and recompose — while the router keeps running and
|
|
34563
|
-
* hot-swaps the rewritten supergraph. Rolls the subgraph back if compose fails,
|
|
34564
|
-
* so a failed boot or reload never orphans the port.
|
|
34788
|
+
* Loads the declaration, lints it, captures its secrets and starts its subgraph server. The
|
|
34789
|
+
* server starts after the load and the lint, so a throw here leaves nothing listening. The lint's
|
|
34790
|
+
* `TypeScriptUnavailableError` is the one caught error: the lint did not run (`capability` is
|
|
34791
|
+
* empty) and the subgraph still serves, the way `validateExtension` reports it.
|
|
34565
34792
|
*
|
|
34566
|
-
* The
|
|
34567
|
-
*
|
|
34793
|
+
* The declared secrets come from the map the caller passed, on boot and on every reload alike.
|
|
34794
|
+
* Nothing is scrubbed: the runner never touches `process.env`, and the capability lint's
|
|
34795
|
+
* `process-env` rule is the guard against a resolver that reads the developer's shell (ADR-0026).
|
|
34568
34796
|
*
|
|
34569
|
-
*
|
|
34570
|
-
*
|
|
34571
|
-
* rewrites no artifact and the store keeps compiling against the last good
|
|
34572
|
-
* `public.graphql`.
|
|
34797
|
+
* The server runtime (Apollo, Express) is imported here and nowhere earlier, so
|
|
34798
|
+
* `@vtex/faststore/cli` loads without it and only `dev` pays for it.
|
|
34573
34799
|
*/
|
|
34574
|
-
async function
|
|
34575
|
-
const definition = await loadDeclaration(extensionRoot,
|
|
34800
|
+
async function startSubgraph(extensionRoot, { port, variables, log }) {
|
|
34801
|
+
const definition = await loadDeclaration(extensionRoot, "dev");
|
|
34576
34802
|
const name = extensionName(extensionRoot, definition);
|
|
34577
|
-
|
|
34803
|
+
let capability = [];
|
|
34804
|
+
let typescript;
|
|
34805
|
+
try {
|
|
34806
|
+
capability = await lintExtension(resolveDeclarationEntry(extensionRoot));
|
|
34807
|
+
} catch (error) {
|
|
34808
|
+
if (!(error instanceof TypeScriptUnavailableError)) throw error;
|
|
34809
|
+
typescript = error;
|
|
34810
|
+
}
|
|
34578
34811
|
const secretNames = definition.secrets ?? [];
|
|
34579
|
-
const secrets = captureSecrets(secretNames,
|
|
34580
|
-
|
|
34581
|
-
|
|
34812
|
+
const secrets = captureSecrets(secretNames, variables);
|
|
34813
|
+
for (const line of unsetSecretWarnings(secretNames, secrets, "dev")) log.warn(line);
|
|
34814
|
+
const sdl = printSubgraphSdl(definition);
|
|
34815
|
+
writeFileSync(join(scratchDir(extensionRoot), "extension.graphql"), sdl, "utf-8");
|
|
34582
34816
|
const { startSubgraphServer } = await import("./server-runtime.js");
|
|
34583
|
-
const
|
|
34817
|
+
const server = await startSubgraphServer(definition, port, {
|
|
34584
34818
|
secretNames,
|
|
34585
34819
|
secrets
|
|
34820
|
+
}, (error) => {
|
|
34821
|
+
log.error(`storefront dev: subgraph server error after startup: ${error.message}`);
|
|
34586
34822
|
});
|
|
34587
|
-
|
|
34588
|
-
|
|
34589
|
-
|
|
34590
|
-
|
|
34591
|
-
|
|
34592
|
-
|
|
34593
|
-
|
|
34594
|
-
|
|
34595
|
-
|
|
34596
|
-
|
|
34597
|
-
url: subgraph.url,
|
|
34598
|
-
sdl: extensionSdl
|
|
34599
|
-
}]);
|
|
34600
|
-
if (!composed.success) throw new Error(formatCompositionErrors("dev", composed.errors));
|
|
34601
|
-
const { supergraphFile } = writeComposedArtifacts(extensionRoot, composed, "dev");
|
|
34602
|
-
return {
|
|
34603
|
-
subgraph,
|
|
34604
|
-
name,
|
|
34605
|
-
supergraphFile
|
|
34606
|
-
};
|
|
34607
|
-
} catch (error) {
|
|
34608
|
-
await subgraph.stop();
|
|
34609
|
-
throw error;
|
|
34610
|
-
}
|
|
34823
|
+
return {
|
|
34824
|
+
root: extensionRoot,
|
|
34825
|
+
name,
|
|
34826
|
+
url: server.url,
|
|
34827
|
+
sdl,
|
|
34828
|
+
secretNames,
|
|
34829
|
+
capability,
|
|
34830
|
+
typescript,
|
|
34831
|
+
stop: server.stop
|
|
34832
|
+
};
|
|
34611
34833
|
}
|
|
34612
34834
|
/**
|
|
34613
|
-
*
|
|
34614
|
-
*
|
|
34615
|
-
*
|
|
34616
|
-
*
|
|
34617
|
-
*
|
|
34618
|
-
|
|
34835
|
+
* Composes the embedded core SDL with every running subgraph and writes the composed artifacts
|
|
34836
|
+
* (`supergraph.graphql` + `public.graphql`) into the session root's scratch. Composition is a
|
|
34837
|
+
* function call, in process and offline. A failure throws with every error and writes nothing,
|
|
34838
|
+
* so the router keeps serving the last good supergraph. Zero subgraphs is a valid input: core
|
|
34839
|
+
* alone composes, so a session with no API Asset still gets a router to point the store at.
|
|
34840
|
+
*/
|
|
34841
|
+
function composeSession(sessionRoot, env, subgraphs, log) {
|
|
34842
|
+
const composed = (0, import_compose.composeSupergraph)([{
|
|
34843
|
+
name: import_subgraph_names.CORE_SUBGRAPH_NAME,
|
|
34844
|
+
url: resolveCoreUrl(env),
|
|
34845
|
+
sdl: coreFederationSdl()
|
|
34846
|
+
}, ...subgraphs.map(({ name, url, sdl }) => ({
|
|
34847
|
+
name,
|
|
34848
|
+
url,
|
|
34849
|
+
sdl
|
|
34850
|
+
}))]);
|
|
34851
|
+
if (!composed.success) throw new Error(formatCompositionErrors("dev", composed.errors));
|
|
34852
|
+
return writeComposedArtifacts(sessionRoot, composed, "dev", log);
|
|
34853
|
+
}
|
|
34854
|
+
/** How many of the router's last stderr lines a boot failure quotes. */
|
|
34855
|
+
const ROUTER_STDERR_TAIL = 5;
|
|
34856
|
+
/** `variables` without `secretNames`: the environment the router child inherits. */
|
|
34857
|
+
function routerEnvironment(variables, secretNames) {
|
|
34858
|
+
const withheld = new Set(secretNames);
|
|
34859
|
+
return Object.fromEntries(Object.entries(variables).filter(([name]) => !withheld.has(name)));
|
|
34860
|
+
}
|
|
34861
|
+
/**
|
|
34862
|
+
* Resolves the pinned Hive Router binary into the session root's scratch, writes the router
|
|
34863
|
+
* config there with one header-rule entry per local subgraph, launches the router against the
|
|
34864
|
+
* composed supergraph and resolves once it answers at its URL, so a caller that reports `ready`
|
|
34865
|
+
* on return is telling the truth. A URL that already answers before the spawn (a router an
|
|
34866
|
+
* earlier session left behind), a router that exits first or one that stays silent past
|
|
34867
|
+
* `ROUTER_LISTEN_TIMEOUT_MS` is a throw, with nothing left running. `stop` SIGTERMs the child
|
|
34868
|
+
* and waits for it to exit.
|
|
34619
34869
|
*
|
|
34620
|
-
* `
|
|
34621
|
-
*
|
|
34622
|
-
*
|
|
34870
|
+
* The router reads `ROUTER_CONFIG_FILE_PATH` and nothing else of its environment, which is the
|
|
34871
|
+
* host's map minus `withholdSecrets`: a resolver's secret has no business in the router's
|
|
34872
|
+
* process. `VTEX_TOKEN` is not a declared secret and goes to the router by design, in the config
|
|
34873
|
+
* as the `authorization` default. The environment is fixed at the spawn: a secret declared later
|
|
34874
|
+
* is withheld once the session restarts.
|
|
34623
34875
|
*/
|
|
34624
|
-
async function
|
|
34625
|
-
|
|
34626
|
-
const
|
|
34627
|
-
|
|
34628
|
-
|
|
34629
|
-
|
|
34630
|
-
|
|
34631
|
-
|
|
34632
|
-
|
|
34633
|
-
|
|
34634
|
-
|
|
34635
|
-
|
|
34636
|
-
|
|
34637
|
-
|
|
34638
|
-
|
|
34639
|
-
|
|
34876
|
+
async function startRouter(sessionRoot, env, subgraphNames, supergraphFile, { log, watchSupergraphSeconds, onExit, withholdSecrets = [] }) {
|
|
34877
|
+
const scratch = scratchDir(sessionRoot);
|
|
34878
|
+
const binaryPath = await ensureRouterBinary(routerBinaryPath(sessionRoot, env.routerVersion), env.routerVersion, env.routerBinary);
|
|
34879
|
+
if (env.insecureSkipTlsVerify) log.warn("storefront dev: ⚠ TLS certificate verification is DISABLED for the core subgraph (VTEX_STOREFRONT_INSECURE_SKIP_TLS_VERIFY). Local dev only — never rely on this in production.");
|
|
34880
|
+
const routerConfig = buildRouterConfig(env, subgraphNames);
|
|
34881
|
+
const routerUrl = `http://${routerConfig.host}:${routerConfig.port}/graphql`;
|
|
34882
|
+
if (await routerAnswersAt(routerUrl)) throw new Error(`storefront dev: something already answers at ${routerUrl} — a router left behind by a session that died? Stop it, or set VTEX_STOREFRONT_ROUTER_PORT to a free port.`);
|
|
34883
|
+
const routerConfigPath = join(scratch, "router.config.yaml");
|
|
34884
|
+
const routerConfigYaml = printRouterConfigYaml({
|
|
34885
|
+
...routerConfig,
|
|
34886
|
+
supergraphPath: resolve(supergraphFile),
|
|
34887
|
+
supergraphPollIntervalSeconds: watchSupergraphSeconds
|
|
34888
|
+
});
|
|
34889
|
+
writeFileSync(routerConfigPath, routerConfigYaml, "utf-8");
|
|
34890
|
+
const recentStderr = [];
|
|
34891
|
+
const tailingLog = {
|
|
34892
|
+
...log,
|
|
34893
|
+
error: (line) => {
|
|
34894
|
+
recentStderr.push(line);
|
|
34895
|
+
if (recentStderr.length > ROUTER_STDERR_TAIL) recentStderr.shift();
|
|
34896
|
+
log.error(line);
|
|
34897
|
+
}
|
|
34898
|
+
};
|
|
34899
|
+
const child = await spawnRouter(binaryPath, routerConfigPath, {
|
|
34900
|
+
env: routerEnvironment(env.variables, withholdSecrets),
|
|
34901
|
+
log: tailingLog
|
|
34902
|
+
});
|
|
34903
|
+
let phase = "booting";
|
|
34904
|
+
child.once("exit", (code, signal) => {
|
|
34905
|
+
if (phase === "up") onExit?.({
|
|
34906
|
+
code,
|
|
34907
|
+
signal
|
|
34640
34908
|
});
|
|
34641
|
-
|
|
34642
|
-
|
|
34909
|
+
});
|
|
34910
|
+
try {
|
|
34911
|
+
await waitForRouterListening(child, routerUrl, () => recentStderr);
|
|
34643
34912
|
} catch (error) {
|
|
34644
|
-
await
|
|
34913
|
+
await stopRouter(child);
|
|
34645
34914
|
throw error;
|
|
34646
34915
|
}
|
|
34647
|
-
|
|
34648
|
-
let stopped = false;
|
|
34649
|
-
let reloading = Promise.resolve();
|
|
34650
|
-
const stop = async () => {
|
|
34651
|
-
if (stopped) return;
|
|
34652
|
-
stopped = true;
|
|
34653
|
-
if (registerSignals) {
|
|
34654
|
-
process.off("SIGINT", teardownOnSignal);
|
|
34655
|
-
process.off("SIGTERM", teardownOnSignal);
|
|
34656
|
-
}
|
|
34657
|
-
await stopRouter(routerChild);
|
|
34658
|
-
await reloading;
|
|
34659
|
-
await currentSubgraph.stop();
|
|
34660
|
-
};
|
|
34661
|
-
function teardownOnSignal() {
|
|
34662
|
-
stop().finally(() => process.exit(0));
|
|
34663
|
-
}
|
|
34664
|
-
if (registerSignals) {
|
|
34665
|
-
process.on("SIGINT", teardownOnSignal);
|
|
34666
|
-
process.on("SIGTERM", teardownOnSignal);
|
|
34667
|
-
}
|
|
34668
|
-
const NOOP_SUBGRAPH = {
|
|
34669
|
-
url: "",
|
|
34670
|
-
stop: () => Promise.resolve()
|
|
34671
|
-
};
|
|
34672
|
-
/**
|
|
34673
|
-
* Watch-mode reload: restart the subgraph (new resolvers/schema) and
|
|
34674
|
-
* recompose `supergraph.graphql` in place. The running router polls that file
|
|
34675
|
-
* and hot-swaps — it is never restarted here.
|
|
34676
|
-
*
|
|
34677
|
-
* The old subgraph is stopped *before* the new one starts: both bind the
|
|
34678
|
-
* same `subgraphPort`, so start-before-stop would `EADDRINUSE`. This leaves a
|
|
34679
|
-
* brief window with no subgraph, which is fine — the router only re-reads the
|
|
34680
|
-
* recomposed supergraph on its next poll anyway. On failure the old
|
|
34681
|
-
* subgraph is already stopped, so teardown must not re-stop it.
|
|
34682
|
-
*
|
|
34683
|
-
* Reloads run one at a time: each waits for the one before it, so two calls
|
|
34684
|
-
* never both stop the same subgraph and start one each.
|
|
34685
|
-
*/
|
|
34686
|
-
const reloadOnce = async () => {
|
|
34687
|
-
if (stopped) return;
|
|
34688
|
-
await currentSubgraph.stop();
|
|
34689
|
-
currentSubgraph = NOOP_SUBGRAPH;
|
|
34690
|
-
const next = await startSubgraphAndCompose(extensionRoot, host, env, secretSource);
|
|
34691
|
-
if (stopped) {
|
|
34692
|
-
await next.subgraph.stop();
|
|
34693
|
-
return;
|
|
34694
|
-
}
|
|
34695
|
-
currentSubgraph = next.subgraph;
|
|
34696
|
-
};
|
|
34697
|
-
const reload = () => {
|
|
34698
|
-
const run = reloading.then(reloadOnce);
|
|
34699
|
-
reloading = run.catch(() => void 0);
|
|
34700
|
-
return run;
|
|
34701
|
-
};
|
|
34916
|
+
phase = "up";
|
|
34702
34917
|
return {
|
|
34703
|
-
routerUrl
|
|
34704
|
-
|
|
34705
|
-
|
|
34918
|
+
routerUrl,
|
|
34919
|
+
child,
|
|
34920
|
+
stop: () => {
|
|
34921
|
+
phase = "stopping";
|
|
34922
|
+
return stopRouter(child);
|
|
34923
|
+
}
|
|
34706
34924
|
};
|
|
34707
34925
|
}
|
|
34708
34926
|
/**
|
|
34709
34927
|
* How long to wait for the router child to exit after SIGTERM before moving
|
|
34710
34928
|
* on. On reload the router must fully release its listen port before the next
|
|
34711
34929
|
* run re-binds it; awaiting exit avoids an `EADDRINUSE` race. Bounded so a
|
|
34712
|
-
* wedged child can't hang teardown forever
|
|
34930
|
+
* wedged child can't hang teardown forever: one still up at the deadline is
|
|
34931
|
+
* SIGKILLed, so it never outlives the session holding the port.
|
|
34713
34932
|
*/
|
|
34714
34933
|
const ROUTER_EXIT_TIMEOUT_MS = 5e3;
|
|
34715
|
-
/** SIGTERMs the router child and resolves once it exits (or the timeout elapses). */
|
|
34934
|
+
/** SIGTERMs the router child and resolves once it exits (or the timeout elapses, after a SIGKILL). */
|
|
34716
34935
|
function stopRouter(routerChild) {
|
|
34717
34936
|
if ((routerChild.exitCode ?? null) !== null || (routerChild.signalCode ?? null) !== null) {
|
|
34718
34937
|
routerChild.kill("SIGTERM");
|
|
34719
34938
|
return Promise.resolve();
|
|
34720
34939
|
}
|
|
34721
34940
|
return new Promise((resolve) => {
|
|
34722
|
-
const timer = setTimeout(
|
|
34941
|
+
const timer = setTimeout(() => {
|
|
34942
|
+
routerChild.kill("SIGKILL");
|
|
34943
|
+
resolve();
|
|
34944
|
+
}, ROUTER_EXIT_TIMEOUT_MS);
|
|
34723
34945
|
timer.unref?.();
|
|
34724
34946
|
routerChild.once("exit", () => {
|
|
34725
34947
|
clearTimeout(timer);
|
|
@@ -34728,10 +34950,6 @@ function stopRouter(routerChild) {
|
|
|
34728
34950
|
routerChild.kill("SIGTERM");
|
|
34729
34951
|
});
|
|
34730
34952
|
}
|
|
34731
|
-
/** Coalesces bursts of file events (an editor save can fire several) into one reload. */
|
|
34732
|
-
const RELOAD_DEBOUNCE_MS = 150;
|
|
34733
|
-
/** How often the router polls its supergraph file for changes, in watch mode. */
|
|
34734
|
-
const ROUTER_WATCH_INTERVAL_SECONDS = 1;
|
|
34735
34953
|
/**
|
|
34736
34954
|
* Directories whose contents never mean "the developer edited the extension".
|
|
34737
34955
|
* `.vtex/` matters most: `dev` writes the composed supergraph, the router
|
|
@@ -34759,6 +34977,121 @@ function isIgnoredWatchPath(filename) {
|
|
|
34759
34977
|
if (!filename) return false;
|
|
34760
34978
|
return filename.split(/[\\/]/).some((segment) => IGNORED_WATCH_DIRS.has(segment));
|
|
34761
34979
|
}
|
|
34980
|
+
//#endregion
|
|
34981
|
+
//#region src/cli/dev.ts
|
|
34982
|
+
/**
|
|
34983
|
+
* Starts the extension's subgraph and composes it with the embedded core SDL
|
|
34984
|
+
* into the extension's own scratch: the single-member session, where the
|
|
34985
|
+
* session root is the extension root. Isolated as one step because watch mode
|
|
34986
|
+
* re-runs exactly this on save, while the router keeps running and hot-swaps
|
|
34987
|
+
* the rewritten supergraph. Rolls the subgraph back if compose fails, so a
|
|
34988
|
+
* failed boot or reload never orphans the port, and a broken schema rewrites
|
|
34989
|
+
* no artifact: the store keeps compiling against the last good `public.graphql`.
|
|
34990
|
+
*
|
|
34991
|
+
* Loud, non-blocking capability-lint feedback: each finding is a warning, never
|
|
34992
|
+
* a failure — `dev` stays iterable mid-fix; the blocking gate is `validate` (and
|
|
34993
|
+
* the future publish flow). See ADR-0002. A host without TypeScript 5 gets one
|
|
34994
|
+
* warning that the lint did not run, and the loop runs.
|
|
34995
|
+
*/
|
|
34996
|
+
async function startSubgraphAndCompose(extensionRoot, env, log) {
|
|
34997
|
+
const subgraph = await startSubgraph(extensionRoot, {
|
|
34998
|
+
port: env.subgraphPort,
|
|
34999
|
+
variables: env.variables,
|
|
35000
|
+
log
|
|
35001
|
+
});
|
|
35002
|
+
if (subgraph.typescript) log.warn(subgraph.typescript.message);
|
|
35003
|
+
for (const finding of subgraph.capability) log.warn(`storefront dev: ⚠ capability lint — ${formatFinding(finding)}. This will FAIL publish; fix before shipping.`);
|
|
35004
|
+
try {
|
|
35005
|
+
const { supergraphFile } = composeSession(extensionRoot, env, [subgraph], log);
|
|
35006
|
+
return {
|
|
35007
|
+
subgraph,
|
|
35008
|
+
supergraphFile
|
|
35009
|
+
};
|
|
35010
|
+
} catch (error) {
|
|
35011
|
+
await subgraph.stop();
|
|
35012
|
+
throw error;
|
|
35013
|
+
}
|
|
35014
|
+
}
|
|
35015
|
+
/**
|
|
35016
|
+
* Orchestrates the full `runDev` loop: loads the declaration, starts
|
|
35017
|
+
* the subgraph server, composes it offline with the embedded core SDL, resolves
|
|
35018
|
+
* the pinned Hive Router binary, and launches the router against the composed
|
|
35019
|
+
* supergraph. Returns a handle so callers (the host, or tests) can await
|
|
35020
|
+
* readiness and tear the loop down. The host passes `env` (`loadRunnerEnv` over
|
|
35021
|
+
* the map it assembled) and owns signals and exit: `stop()` is the only
|
|
35022
|
+
* teardown (ADR-0026).
|
|
35023
|
+
*
|
|
35024
|
+
* `watchSupergraphSeconds` writes the router config with a supergraph poll
|
|
35025
|
+
* interval, so a subsequently recomposed `supergraph.graphql` is hot-swapped in
|
|
35026
|
+
* place (watch mode uses this instead of restarting the router).
|
|
35027
|
+
*/
|
|
35028
|
+
async function runDev(extensionRoot, env, { log = SILENT_LOG, watchSupergraphSeconds } = {}) {
|
|
35029
|
+
for (const line of renamedVariableWarnings(env.variables)) log.warn(line);
|
|
35030
|
+
const { subgraph, supergraphFile } = await startSubgraphAndCompose(extensionRoot, env, log);
|
|
35031
|
+
let router;
|
|
35032
|
+
try {
|
|
35033
|
+
router = await startRouter(extensionRoot, env, [subgraph.name], supergraphFile, {
|
|
35034
|
+
log,
|
|
35035
|
+
watchSupergraphSeconds,
|
|
35036
|
+
withholdSecrets: subgraph.secretNames,
|
|
35037
|
+
onExit: (exit) => {
|
|
35038
|
+
log.error(`storefront dev: the Hive Router exited ${describeRouterExit(exit)}; the loop is down — stop it and start it again.`);
|
|
35039
|
+
}
|
|
35040
|
+
});
|
|
35041
|
+
} catch (error) {
|
|
35042
|
+
await subgraph.stop();
|
|
35043
|
+
throw error;
|
|
35044
|
+
}
|
|
35045
|
+
let currentSubgraph = subgraph;
|
|
35046
|
+
let stopped = false;
|
|
35047
|
+
let reloading = Promise.resolve();
|
|
35048
|
+
let stopping;
|
|
35049
|
+
const stop = () => {
|
|
35050
|
+
stopping ??= (async () => {
|
|
35051
|
+
stopped = true;
|
|
35052
|
+
await router.stop();
|
|
35053
|
+
await reloading;
|
|
35054
|
+
await currentSubgraph.stop();
|
|
35055
|
+
})();
|
|
35056
|
+
return stopping;
|
|
35057
|
+
};
|
|
35058
|
+
const NOOP_SUBGRAPH = { stop: () => Promise.resolve() };
|
|
35059
|
+
/**
|
|
35060
|
+
* Watch-mode reload: restart the subgraph (new resolvers/schema) and
|
|
35061
|
+
* recompose `supergraph.graphql` in place. The running router polls that file
|
|
35062
|
+
* and hot-swaps — it is never restarted here.
|
|
35063
|
+
*
|
|
35064
|
+
* The old subgraph is stopped *before* the new one starts: both bind the
|
|
35065
|
+
* same `subgraphPort`, so start-before-stop would `EADDRINUSE`. This leaves a
|
|
35066
|
+
* brief window with no subgraph, which is fine — the router only re-reads the
|
|
35067
|
+
* recomposed supergraph on its next poll anyway. On failure the old
|
|
35068
|
+
* subgraph is already stopped, so teardown must not re-stop it.
|
|
35069
|
+
*
|
|
35070
|
+
* Reloads run one at a time: each waits for the one before it, so two calls
|
|
35071
|
+
* never both stop the same subgraph and start one each.
|
|
35072
|
+
*/
|
|
35073
|
+
const reloadOnce = async () => {
|
|
35074
|
+
if (stopped) return;
|
|
35075
|
+
await currentSubgraph.stop();
|
|
35076
|
+
currentSubgraph = NOOP_SUBGRAPH;
|
|
35077
|
+
const next = await startSubgraphAndCompose(extensionRoot, env, log);
|
|
35078
|
+
if (stopped) {
|
|
35079
|
+
await next.subgraph.stop();
|
|
35080
|
+
return;
|
|
35081
|
+
}
|
|
35082
|
+
currentSubgraph = next.subgraph;
|
|
35083
|
+
};
|
|
35084
|
+
const reload = () => {
|
|
35085
|
+
const run = reloading.then(reloadOnce);
|
|
35086
|
+
reloading = run.catch(() => void 0);
|
|
35087
|
+
return run;
|
|
35088
|
+
};
|
|
35089
|
+
return {
|
|
35090
|
+
routerUrl: router.routerUrl,
|
|
35091
|
+
stop,
|
|
35092
|
+
reload
|
|
35093
|
+
};
|
|
35094
|
+
}
|
|
34762
35095
|
/**
|
|
34763
35096
|
* Runs the dev loop under a file watcher rooted at the declaration entry's
|
|
34764
35097
|
* directory. On save it restarts the subgraph (new resolvers/schema) and
|
|
@@ -34771,13 +35104,12 @@ function isIgnoredWatchPath(filename) {
|
|
|
34771
35104
|
* change callback — otherwise the run's own writes into `.vtex/storefront/`
|
|
34772
35105
|
* would feed the loop back into itself.
|
|
34773
35106
|
*
|
|
34774
|
-
*
|
|
34775
|
-
* with `registerSignals: false`).
|
|
35107
|
+
* Like `runDev`, it installs no signal handler: the host calls `stop()`.
|
|
34776
35108
|
*/
|
|
34777
|
-
async function runDevWatch(extensionRoot,
|
|
34778
|
-
const current = await runDev(extensionRoot,
|
|
34779
|
-
|
|
34780
|
-
watchSupergraphSeconds:
|
|
35109
|
+
async function runDevWatch(extensionRoot, env, { log = SILENT_LOG } = {}) {
|
|
35110
|
+
const current = await runDev(extensionRoot, env, {
|
|
35111
|
+
log,
|
|
35112
|
+
watchSupergraphSeconds: 1
|
|
34781
35113
|
});
|
|
34782
35114
|
const watchDir = resolveWatchRoot(extensionRoot);
|
|
34783
35115
|
let watcher;
|
|
@@ -34795,9 +35127,9 @@ async function runDevWatch(extensionRoot, host, env = loadExtensionEnv(extension
|
|
|
34795
35127
|
pending = false;
|
|
34796
35128
|
try {
|
|
34797
35129
|
await current.reload();
|
|
34798
|
-
|
|
35130
|
+
log.info("storefront dev: reloaded — router hot-swapped the recomposed schema in place.");
|
|
34799
35131
|
} catch (error) {
|
|
34800
|
-
|
|
35132
|
+
log.error(`storefront dev: reload failed:\n${error instanceof Error ? error.message : String(error)}`);
|
|
34801
35133
|
}
|
|
34802
35134
|
} while (pending && !stopped);
|
|
34803
35135
|
reloading = false;
|
|
@@ -34806,54 +35138,1018 @@ async function runDevWatch(extensionRoot, host, env = loadExtensionEnv(extension
|
|
|
34806
35138
|
if (stopped) return;
|
|
34807
35139
|
if (isIgnoredWatchPath(typeof filename === "string" ? filename : filename?.toString() ?? null)) return;
|
|
34808
35140
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
34809
|
-
reloadTimer = setTimeout(reload,
|
|
35141
|
+
reloadTimer = setTimeout(reload, 150);
|
|
34810
35142
|
};
|
|
34811
|
-
if (existsSync(watchDir))
|
|
34812
|
-
|
|
34813
|
-
|
|
34814
|
-
|
|
34815
|
-
|
|
34816
|
-
|
|
34817
|
-
|
|
34818
|
-
|
|
34819
|
-
|
|
34820
|
-
|
|
35143
|
+
if (existsSync(watchDir)) {
|
|
35144
|
+
try {
|
|
35145
|
+
watcher = watch(watchDir, { recursive: true }, scheduleReload);
|
|
35146
|
+
} catch (error) {
|
|
35147
|
+
await current.stop();
|
|
35148
|
+
throw error;
|
|
35149
|
+
}
|
|
35150
|
+
watcher.on("error", (error) => {
|
|
35151
|
+
log.error(`storefront dev: watcher error on ${watchDir}: ${error.message}`);
|
|
35152
|
+
});
|
|
35153
|
+
} else log.warn(`storefront dev: ${watchDir} not found; watch mode is idle (no files to watch).`);
|
|
35154
|
+
let stopping;
|
|
35155
|
+
const stop = () => {
|
|
35156
|
+
stopping ??= (async () => {
|
|
35157
|
+
stopped = true;
|
|
35158
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
35159
|
+
watcher?.close();
|
|
35160
|
+
await current.stop();
|
|
35161
|
+
})();
|
|
35162
|
+
return stopping;
|
|
34821
35163
|
};
|
|
34822
|
-
function teardownOnSignal() {
|
|
34823
|
-
stop().finally(() => process.exit(0));
|
|
34824
|
-
}
|
|
34825
|
-
process.on("SIGINT", teardownOnSignal);
|
|
34826
|
-
process.on("SIGTERM", teardownOnSignal);
|
|
34827
35164
|
return {
|
|
34828
35165
|
routerUrl: current.routerUrl,
|
|
34829
35166
|
stop
|
|
34830
35167
|
};
|
|
34831
35168
|
}
|
|
34832
35169
|
//#endregion
|
|
34833
|
-
//#region src/cli/
|
|
34834
|
-
/** The registry semver range the scaffold pins `@vtex/faststore` to (future-correct shape, not `workspace:*`). */
|
|
34835
|
-
const SDK_VERSION_RANGE = "^0.0.0";
|
|
35170
|
+
//#region src/cli/provider-findings.ts
|
|
34836
35171
|
/**
|
|
34837
|
-
*
|
|
34838
|
-
*
|
|
34839
|
-
*
|
|
35172
|
+
* The Finding builders `validate`, `dev` and `build` share: one code per fault, whichever verb
|
|
35173
|
+
* finds it, so a reload reports the same Finding the lint would. Each Finding points at the source
|
|
35174
|
+
* file it is about, relative to the Account Repo root, so `vtex extension` renders it as
|
|
35175
|
+
* `file:line:column`.
|
|
34840
35176
|
*/
|
|
34841
|
-
|
|
34842
|
-
|
|
34843
|
-
|
|
34844
|
-
|
|
34845
|
-
|
|
34846
|
-
|
|
34847
|
-
|
|
34848
|
-
|
|
34849
|
-
|
|
34850
|
-
|
|
34851
|
-
|
|
34852
|
-
|
|
34853
|
-
|
|
35177
|
+
/** The reference doc on `main`, one section per Finding family. */
|
|
35178
|
+
const REFERENCE_DOC = "https://github.com/vtex/faststore-ai-native/blob/main/docs/storefront-api-core/reference-extension-sdk.md";
|
|
35179
|
+
const CAPABILITY_DOCS = `${REFERENCE_DOC}#capability-boundary`;
|
|
35180
|
+
const CONTRACT_DOCS = `${REFERENCE_DOC}#the-core-contract`;
|
|
35181
|
+
const LIFECYCLE_DOCS = `${REFERENCE_DOC}#the-lifecycle-functions`;
|
|
35182
|
+
/** The UI side's docs on `main`: why the export is the UI signal, and what the catalog build rejects. */
|
|
35183
|
+
const COMPILER_DOCS = "https://github.com/vtex/faststore-ai-native/blob/main/docs/storefront-compiler";
|
|
35184
|
+
const CATALOG_EXPORT_DOCS = `${COMPILER_DOCS}/adr-0007-classify-a-ui-extension-by-its-catalog-export.md`;
|
|
35185
|
+
const CATALOG_BUILD_DOCS = `${COMPILER_DOCS}/reference-catalog-build.md#failure-discipline`;
|
|
35186
|
+
/** What to reach for instead, per capability rule (`reference-extension-sdk.md › Capability boundary`). */
|
|
35187
|
+
const CAPABILITY_ADVICE = {
|
|
35188
|
+
"process-env": "read secrets through `ctx.secrets`",
|
|
35189
|
+
"fs-import": "a resolver has no filesystem",
|
|
35190
|
+
"child-process-import": "a resolver spawns no child process"
|
|
35191
|
+
};
|
|
35192
|
+
/**
|
|
35193
|
+
* `absolute` relative to the Account Repo root with `/` separators, as a Finding's `file`, or
|
|
35194
|
+
* `undefined` when it lies outside the root: the core rejects `..` (`isRepoFile`).
|
|
35195
|
+
*/
|
|
35196
|
+
function repoFile(root, absolute) {
|
|
35197
|
+
const file = relative(root, absolute).split(sep).join("/");
|
|
35198
|
+
return file === ".." || file.startsWith("../") ? void 0 : file;
|
|
35199
|
+
}
|
|
35200
|
+
/**
|
|
35201
|
+
* `message` with every path under the repo root made relative to it. The addon's and the runner's
|
|
35202
|
+
* errors name files as the machine spells them (`NoExtensionComponents { components_dir }`, a
|
|
35203
|
+
* TypeScript diagnostic), and a Finding's message reaches `--json` and the pipeline.
|
|
35204
|
+
*/
|
|
35205
|
+
function repoRelativeMessage(root, message) {
|
|
35206
|
+
const prefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
35207
|
+
return message.split(prefix).join("");
|
|
35208
|
+
}
|
|
35209
|
+
/**
|
|
35210
|
+
* One `storefront-capability-<rule>` Finding on the source line the lint flagged. A file outside
|
|
35211
|
+
* the repo root cannot be the Finding's `file`, so it lands on the declaration entry and the
|
|
35212
|
+
* message names the file relative to the root (`../shared/x.ts:3:5`), never as the machine spells
|
|
35213
|
+
* it: the message reaches `--json` and the pipeline.
|
|
35214
|
+
*/
|
|
35215
|
+
function capabilityFinding(root, entryFile, { file, rule, line, column }) {
|
|
35216
|
+
const inRepo = repoFile(root, file);
|
|
35217
|
+
const where = inRepo === void 0 ? ` at ${relative(root, file).split(sep).join("/")}:${line}:${column}` : "";
|
|
35218
|
+
return {
|
|
35219
|
+
code: `storefront-capability-${rule}`,
|
|
35220
|
+
severity: "error",
|
|
35221
|
+
message: `forbidden ${rule}${where}: ${CAPABILITY_ADVICE[rule]}`,
|
|
35222
|
+
path: "",
|
|
35223
|
+
file: inRepo ?? entryFile,
|
|
35224
|
+
range: {
|
|
35225
|
+
start: {
|
|
35226
|
+
line,
|
|
35227
|
+
column
|
|
35228
|
+
},
|
|
35229
|
+
end: {
|
|
35230
|
+
line,
|
|
35231
|
+
column
|
|
35232
|
+
}
|
|
35233
|
+
},
|
|
35234
|
+
docs: CAPABILITY_DOCS
|
|
35235
|
+
};
|
|
35236
|
+
}
|
|
35237
|
+
/** The capability lint did not run: the Asset's `typescript` has no TypeScript 5 API. On its `package.json`. */
|
|
35238
|
+
function typescriptUnsupported(file, error) {
|
|
35239
|
+
return {
|
|
35240
|
+
code: "storefront-typescript-unsupported",
|
|
35241
|
+
severity: "error",
|
|
35242
|
+
message: `${error.message} Add \`typescript@^5.9.3\` to the Asset's devDependencies; the capability lint did not run.`,
|
|
35243
|
+
path: "",
|
|
35244
|
+
file,
|
|
35245
|
+
docs: LIFECYCLE_DOCS
|
|
35246
|
+
};
|
|
35247
|
+
}
|
|
35248
|
+
/** The declaration did not load or build. On the declaration entry. */
|
|
35249
|
+
function declarationInvalid(file, message) {
|
|
35250
|
+
return {
|
|
35251
|
+
code: "storefront-declaration-invalid",
|
|
35252
|
+
severity: "error",
|
|
35253
|
+
path: "",
|
|
35254
|
+
file,
|
|
35255
|
+
message,
|
|
35256
|
+
docs: CONTRACT_DOCS
|
|
35257
|
+
};
|
|
35258
|
+
}
|
|
35259
|
+
/** One composition error of the emitted slice against the core SDL. On the declaration entry. */
|
|
35260
|
+
function compositionFailed(file, message) {
|
|
35261
|
+
return {
|
|
35262
|
+
code: "storefront-composition-failed",
|
|
35263
|
+
severity: "error",
|
|
35264
|
+
path: "",
|
|
35265
|
+
file,
|
|
35266
|
+
message,
|
|
35267
|
+
docs: CONTRACT_DOCS
|
|
35268
|
+
};
|
|
35269
|
+
}
|
|
35270
|
+
/**
|
|
35271
|
+
* A UI side without `exports["./catalog.json"]`: Sukima sees a UI Extension through that export
|
|
35272
|
+
* alone (ADR-0007), so no store sees the components, and nothing else reports that. On the Asset's
|
|
35273
|
+
* `package.json`.
|
|
35274
|
+
*/
|
|
35275
|
+
function catalogExportMissing(file) {
|
|
35276
|
+
return {
|
|
35277
|
+
code: "storefront-catalog-export-missing",
|
|
35278
|
+
severity: "error",
|
|
35279
|
+
path: "",
|
|
35280
|
+
file,
|
|
35281
|
+
message: "the package has a UI side (`src/components/`) but its `package.json` has no `exports[\"./catalog.json\"]`, so no store sees its components — add `\"./catalog.json\": \"./dist/catalog.json\"`",
|
|
35282
|
+
docs: CATALOG_EXPORT_DOCS
|
|
35283
|
+
};
|
|
35284
|
+
}
|
|
35285
|
+
/**
|
|
35286
|
+
* A `./catalog.json` export that names no file inside the Asset: `reason` is `catalogExport`'s.
|
|
35287
|
+
* `dev` and `build` write where the export points, so the catalog goes to `dist/` meanwhile, where
|
|
35288
|
+
* no store reads it. On the Asset's `package.json`.
|
|
35289
|
+
*/
|
|
35290
|
+
function catalogExportInvalid(file, reason) {
|
|
35291
|
+
return {
|
|
35292
|
+
code: "storefront-catalog-export-invalid",
|
|
35293
|
+
severity: "error",
|
|
35294
|
+
path: "",
|
|
35295
|
+
file,
|
|
35296
|
+
message: `\`exports["./catalog.json"]\` ${reason}; the catalog is built into \`dist/\` meanwhile, where no store reads it — point the export at a file inside the Asset, \`"./dist/catalog.json"\``,
|
|
35297
|
+
docs: CATALOG_EXPORT_DOCS
|
|
35298
|
+
};
|
|
35299
|
+
}
|
|
35300
|
+
/** The UI side's export Findings, from what `catalogExport` read: one when the export is absent or unusable. */
|
|
35301
|
+
function catalogExportFindings(exported, manifestFile) {
|
|
35302
|
+
if (exported.kind === "none") return [catalogExportMissing(manifestFile)];
|
|
35303
|
+
if (exported.kind === "invalid") return [catalogExportInvalid(manifestFile, exported.reason)];
|
|
35304
|
+
return [];
|
|
35305
|
+
}
|
|
35306
|
+
/** The catalog build threw; `message` is the addon's error, its paths made repo-relative. On the Asset's `package.json`. */
|
|
35307
|
+
function catalogInvalid(file, message) {
|
|
35308
|
+
return {
|
|
35309
|
+
code: "storefront-catalog-invalid",
|
|
35310
|
+
severity: "error",
|
|
35311
|
+
path: "",
|
|
35312
|
+
file,
|
|
35313
|
+
message,
|
|
35314
|
+
docs: CATALOG_BUILD_DOCS
|
|
35315
|
+
};
|
|
35316
|
+
}
|
|
35317
|
+
/**
|
|
35318
|
+
* The build step after `validate` passed failed: a `tsconfig.json` problem, a TypeScript
|
|
35319
|
+
* diagnostic, the server bundle, a component-doc basename collision, the catalog. `message` is
|
|
35320
|
+
* the runner's error, its paths made repo-relative. On the Asset's `package.json`.
|
|
35321
|
+
*/
|
|
35322
|
+
function buildFailed(file, message) {
|
|
35323
|
+
return {
|
|
35324
|
+
code: "storefront-build-failed",
|
|
35325
|
+
severity: "error",
|
|
35326
|
+
path: "",
|
|
35327
|
+
file,
|
|
35328
|
+
message,
|
|
35329
|
+
docs: LIFECYCLE_DOCS
|
|
35330
|
+
};
|
|
35331
|
+
}
|
|
35332
|
+
//#endregion
|
|
35333
|
+
//#region src/cli/provider-log.ts
|
|
35334
|
+
/**
|
|
35335
|
+
* What `dev` and `build` share to report through the contract: the runner's three log levels
|
|
35336
|
+
* into `ctx.log`, and the message of whatever the runner threw. Nothing here prints.
|
|
35337
|
+
*/
|
|
35338
|
+
/** The runner's three levels into the contract's one function, for the session-wide lines. */
|
|
35339
|
+
function contextLog(ctx) {
|
|
35340
|
+
const line = (message) => ctx.log(message);
|
|
35341
|
+
return {
|
|
35342
|
+
info: line,
|
|
35343
|
+
warn: line,
|
|
35344
|
+
error: line
|
|
35345
|
+
};
|
|
35346
|
+
}
|
|
35347
|
+
function errorMessage(error) {
|
|
35348
|
+
return error instanceof Error ? error.message : String(error);
|
|
35349
|
+
}
|
|
35350
|
+
//#endregion
|
|
35351
|
+
//#region src/cli/provider-kind-rules.ts
|
|
35352
|
+
/**
|
|
35353
|
+
* The two kind rules RFC-0003 §The storefront kind leaves to this provider: a Platform Extension
|
|
35354
|
+
* has one storefront Asset, and a store depends only on storefront Assets whose extension its
|
|
35355
|
+
* account installs. Pure over the Asset, `ctx.repo` and `ctx.account`: no filesystem, no runner.
|
|
35356
|
+
* The core's `installed:` and `store:` parsers are copied here because the core is not a
|
|
35357
|
+
* dependency (`src/core/repo.ts` at `e11c964`).
|
|
35358
|
+
*/
|
|
35359
|
+
const KIND_DOCS = "https://github.com/vtex/extensions-platform/blob/main/docs/rfc/0003-extensions-cli.md#the-storefront-kind";
|
|
35360
|
+
/** One `installed:` entry, `vendor.name@RANGE` (`installedPattern`). A range may hold spaces. */
|
|
35361
|
+
const INSTALLED_PATTERN = /^([^.@\s]+)\.([^@\s]+)@(.+)$/;
|
|
35362
|
+
/** `stores/<store>` or `stores/<store>/` → `<store>` (`storeNameOf`). */
|
|
35363
|
+
function storeNameOf(value) {
|
|
35364
|
+
return value === void 0 ? void 0 : /^stores\/([^/.][^/]*)\/?$/.exec(value)?.[1];
|
|
35365
|
+
}
|
|
35366
|
+
/** True when `installed` lists the extension, whatever the range. An extension without `vendor` is never installed. */
|
|
35367
|
+
function installs(installed, extension) {
|
|
35368
|
+
if (extension.vendor === void 0) return false;
|
|
35369
|
+
return installed.some((entry) => {
|
|
35370
|
+
const match = INSTALLED_PATTERN.exec(entry);
|
|
35371
|
+
return match !== null && match[1] === extension.vendor && match[2] === extension.name;
|
|
35372
|
+
});
|
|
35373
|
+
}
|
|
35374
|
+
/**
|
|
35375
|
+
* `storefront-one-per-extension`, the stand-in's rule verbatim: the extension's first inventory
|
|
35376
|
+
* Asset of kind `storefront` is its storefront, and any other one is reported on its own `kind`.
|
|
35377
|
+
* A staged Asset is not in the inventory, so it never counts as the first.
|
|
35378
|
+
*/
|
|
35379
|
+
function onePerExtension(asset, ctx) {
|
|
35380
|
+
const first = ctx.repo.extensions.find(({ path }) => path === asset.extension.path)?.assets.find(({ kind }) => kind === "storefront");
|
|
35381
|
+
if (first === void 0 || first.path === asset.path) return [];
|
|
35382
|
+
return [{
|
|
35383
|
+
code: "storefront-one-per-extension",
|
|
35384
|
+
severity: "error",
|
|
35385
|
+
message: `extension "${asset.extension.name}" has a second storefront Asset "${asset.name}": "${first.name}" (${asset.extension.path}/${first.path}) is its storefront, and an extension has one`,
|
|
35386
|
+
path: "/kind",
|
|
35387
|
+
file: asset.file,
|
|
35388
|
+
docs: KIND_DOCS
|
|
35389
|
+
}];
|
|
35390
|
+
}
|
|
35391
|
+
/**
|
|
35392
|
+
* `storefront-store-extension-not-installed`, one per store that depends on the Asset's package
|
|
35393
|
+
* while an account that names the store (`ctx.account` alone, when given) does not install the
|
|
35394
|
+
* extension. A store no account names is the core's concern. Presence only: the CLI has no range
|
|
35395
|
+
* parser yet (RFC-0003 §Installed ranges).
|
|
35396
|
+
*/
|
|
35397
|
+
function storeExtensionInstalled(asset, ctx) {
|
|
35398
|
+
const packageName = asset.packageJson?.name;
|
|
35399
|
+
if (typeof packageName !== "string") return [];
|
|
35400
|
+
const { vendor, name } = asset.extension;
|
|
35401
|
+
const findings = [];
|
|
35402
|
+
for (const store of ctx.repo.stores) {
|
|
35403
|
+
if (!store.dependencies.includes(packageName)) continue;
|
|
35404
|
+
const missing = ctx.repo.accounts.filter((account) => storeNameOf(account.store) === store.name).filter((account) => ctx.account === void 0 || account.name === ctx.account).filter((account) => !installs(account.installed, asset.extension));
|
|
35405
|
+
if (missing.length === 0) continue;
|
|
35406
|
+
const accounts = missing.map((account) => `"${account.name ?? "(unnamed)"}"`).join(", ");
|
|
35407
|
+
findings.push({
|
|
35408
|
+
code: "storefront-store-extension-not-installed",
|
|
35409
|
+
severity: "error",
|
|
35410
|
+
message: `store "${store.name}" depends on \`${packageName}\`, the storefront Asset of extension "${name}", which account${missing.length === 1 ? "" : "s"} ${accounts} ${missing.length === 1 ? "does" : "do"} not install — add \`${vendor ?? "<vendor>"}.${name}@<range>\` to their \`installed:\` or drop the dependency`,
|
|
35411
|
+
path: "",
|
|
35412
|
+
file: `${store.path}/package.json`,
|
|
35413
|
+
docs: KIND_DOCS
|
|
35414
|
+
});
|
|
35415
|
+
}
|
|
35416
|
+
return findings;
|
|
35417
|
+
}
|
|
35418
|
+
/** The kind rules' Findings for one Asset: one-per-extension, then the store rule. */
|
|
35419
|
+
function kindRuleFindings(asset, ctx) {
|
|
35420
|
+
return [...onePerExtension(asset, ctx), ...storeExtensionInstalled(asset, ctx)];
|
|
35421
|
+
}
|
|
35422
|
+
//#endregion
|
|
35423
|
+
//#region src/cli/provider-validate.ts
|
|
35424
|
+
/**
|
|
35425
|
+
* The provider's `validate`: for each storefront Asset, the two kind rules, then the runner's
|
|
35426
|
+
* `validateExtension` report for its API Extension, then its UI Extension's catalog build, all as
|
|
35427
|
+
* contract Findings (ADR-0026). Each Finding points at the source file it is about, relative to the
|
|
35428
|
+
* Account Repo root, so `vtex extension` renders it as `file:line:column`. Nothing here logs: a
|
|
35429
|
+
* Finding is the only output. Nothing catches around the runner either, except the catalog build
|
|
35430
|
+
* itself (its loader stays outside the `try`): any other throw is `provider-failed` on every
|
|
35431
|
+
* Asset, and a catch would hide a runner bug. The Finding builders live in `provider-findings.ts`,
|
|
35432
|
+
* shared with `dev`. The side predicates live in `asset-layout.ts`, shared with `dev` and `build`.
|
|
35433
|
+
*/
|
|
35434
|
+
/**
|
|
35435
|
+
* The API side of one Asset: the TypeScript Finding, the capability ones, the declaration one,
|
|
35436
|
+
* the composition ones. Nothing for an Asset without an API Extension.
|
|
35437
|
+
*/
|
|
35438
|
+
async function apiFindings(asset, ctx) {
|
|
35439
|
+
const entry = apiDeclarationEntry(asset);
|
|
35440
|
+
if (entry === void 0) return [];
|
|
35441
|
+
const entryFile = repoFile(ctx.root, entry) ?? asset.file;
|
|
35442
|
+
if (!existsSync(entry)) return [declarationInvalid(entryFile, `storefront validate: declaration entry not found at ${entryFile}.`)];
|
|
35443
|
+
const report = await validateExtension(asset.dir);
|
|
35444
|
+
const manifestFile = repoFile(ctx.root, join(asset.dir, "package.json")) ?? asset.file;
|
|
35445
|
+
return [
|
|
35446
|
+
...report.typescript ? [typescriptUnsupported(manifestFile, report.typescript)] : [],
|
|
35447
|
+
...report.capability.map((finding) => capabilityFinding(ctx.root, entryFile, finding)),
|
|
35448
|
+
...report.buildError ? [declarationInvalid(entryFile, report.buildError.message)] : [],
|
|
35449
|
+
...report.composition.map((error) => compositionFailed(entryFile, error.message))
|
|
35450
|
+
];
|
|
35451
|
+
}
|
|
35452
|
+
/**
|
|
35453
|
+
* The UI side of one Asset: the export Finding (absent, or naming no file inside the Asset), then
|
|
35454
|
+
* the catalog build into the Asset's scratch folder, never `dist/`. The build runs even without a
|
|
35455
|
+
* usable export, so a component error and the export error arrive together. Nothing for an Asset
|
|
35456
|
+
* without a UI Extension.
|
|
35457
|
+
*/
|
|
35458
|
+
async function uiFindings(asset, ctx) {
|
|
35459
|
+
if (!uiSide(asset)) return [];
|
|
35460
|
+
const manifestFile = repoFile(ctx.root, join(asset.dir, "package.json")) ?? asset.file;
|
|
35461
|
+
const findings = catalogExportFindings(catalogExport(asset), manifestFile);
|
|
35462
|
+
await loadCatalogBuilder("validate");
|
|
35463
|
+
try {
|
|
35464
|
+
await buildAssetCatalog(asset.dir, catalogDir(asset.dir), "validate");
|
|
35465
|
+
return findings;
|
|
35466
|
+
} catch (error) {
|
|
35467
|
+
return [...findings, catalogInvalid(manifestFile, repoRelativeMessage(ctx.root, errorMessage(error)))];
|
|
35468
|
+
}
|
|
35469
|
+
}
|
|
35470
|
+
/**
|
|
35471
|
+
* Validates each Asset in turn and returns their Findings in Asset order: per Asset, the kind
|
|
35472
|
+
* rules, then the API side, then the UI side. Sequential on purpose: two Assets'
|
|
35473
|
+
* `.vtex/storefront/` writes never interleave.
|
|
35474
|
+
*/
|
|
35475
|
+
async function validateAssets(assets, ctx) {
|
|
35476
|
+
const findings = [];
|
|
35477
|
+
for (const asset of assets) findings.push(...kindRuleFindings(asset, ctx), ...await apiFindings(asset, ctx), ...await uiFindings(asset, ctx));
|
|
35478
|
+
return findings;
|
|
35479
|
+
}
|
|
35480
|
+
//#endregion
|
|
35481
|
+
//#region src/cli/provider-build.ts
|
|
35482
|
+
/**
|
|
35483
|
+
* The provider's `build`: `buildExtension` per storefront Asset, in call order, into the staging
|
|
35484
|
+
* folder the core hands as `outDir` (ADR-0026). The core's own `validate` pass blocks an Asset on
|
|
35485
|
+
* its manifests alone, so each Asset is validated here first: one with a Finding is not built,
|
|
35486
|
+
* and that Finding is its result. A runner throw after that is one `storefront-build-failed` on
|
|
35487
|
+
* the Asset's `package.json`, and the next Asset still builds. The composed-artifact lines reach
|
|
35488
|
+
* `ctx.log`; nothing here prints, and nothing reads `asset.env` or `ctx.env`: the build needs no
|
|
35489
|
+
* variable. Sequential on purpose, as `validate`: two Assets' `.vtex/storefront/` writes never
|
|
35490
|
+
* interleave.
|
|
35491
|
+
*/
|
|
35492
|
+
/** Builds each Asset in turn and returns their Findings in Asset order. */
|
|
35493
|
+
async function buildAssets(assets, ctx) {
|
|
35494
|
+
const findings = [];
|
|
35495
|
+
const log = contextLog(ctx);
|
|
35496
|
+
for (const asset of assets) {
|
|
35497
|
+
const found = await validateAssets([asset], ctx);
|
|
35498
|
+
if (found.length > 0) {
|
|
35499
|
+
findings.push(...found);
|
|
35500
|
+
continue;
|
|
34854
35501
|
}
|
|
34855
|
-
|
|
35502
|
+
const manifestFile = repoFile(ctx.root, join(asset.dir, "package.json")) ?? asset.file;
|
|
35503
|
+
try {
|
|
35504
|
+
const result = await buildExtension(asset.dir, {
|
|
35505
|
+
outDir: asset.outDir,
|
|
35506
|
+
log
|
|
35507
|
+
});
|
|
35508
|
+
const parts = [result.api && "server.mjs + extension.graphql", result.ui && "catalog.json + docs/"];
|
|
35509
|
+
ctx.log(`built ${asset.target}: ${parts.filter(Boolean).join(", ")}`);
|
|
35510
|
+
} catch (error) {
|
|
35511
|
+
findings.push(buildFailed(manifestFile, repoRelativeMessage(ctx.root, errorMessage(error))));
|
|
35512
|
+
}
|
|
35513
|
+
}
|
|
35514
|
+
return findings;
|
|
35515
|
+
}
|
|
35516
|
+
//#endregion
|
|
35517
|
+
//#region src/cli/session-lock.ts
|
|
35518
|
+
/** The lock file inside a session root's scratch: the pid of the `dev` that owns the folder. */
|
|
35519
|
+
const SESSION_LOCK_FILE = "dev.lock";
|
|
35520
|
+
/** How many times a lock file that is there but says nothing is read again, and how far apart, before it counts as stale. */
|
|
35521
|
+
const UNREADABLE_RETRIES = 3;
|
|
35522
|
+
const UNREADABLE_RETRY_MS = 10;
|
|
35523
|
+
/** How many times the whole take is attempted when the file keeps changing under it. */
|
|
35524
|
+
const ATTEMPTS = 5;
|
|
35525
|
+
/** Thrown when a live process already owns the session root's scratch. */
|
|
35526
|
+
var SessionLockedError = class extends Error {
|
|
35527
|
+
ownerPid;
|
|
35528
|
+
constructor(scratch, ownerPid) {
|
|
35529
|
+
const who = ownerPid === pid ? `a second \`dev\` in this process (pid ${pid}), which still holds it: the first one was not stopped, or two SDK versions are among the selected Assets` : `another dev session (pid ${ownerPid})`;
|
|
35530
|
+
super(`storefront dev: ${scratch} is in use by ${who}. Stop it first: two sessions over one root would overwrite each other's supergraph and fight for the router port (${join(scratch, SESSION_LOCK_FILE)}).`);
|
|
35531
|
+
this.name = "SessionLockedError";
|
|
35532
|
+
this.ownerPid = ownerPid;
|
|
35533
|
+
}
|
|
35534
|
+
};
|
|
35535
|
+
function errorCode(error) {
|
|
35536
|
+
return error instanceof Error && "code" in error ? String(error.code) : void 0;
|
|
35537
|
+
}
|
|
35538
|
+
/** True when a process with `candidate` is alive: signal 0 reaches it, or is refused (`EPERM`), which is alive too. */
|
|
35539
|
+
function alive(candidate) {
|
|
35540
|
+
try {
|
|
35541
|
+
kill(candidate, 0);
|
|
35542
|
+
return true;
|
|
35543
|
+
} catch (error) {
|
|
35544
|
+
return errorCode(error) === "EPERM";
|
|
35545
|
+
}
|
|
35546
|
+
}
|
|
35547
|
+
/**
|
|
35548
|
+
* What a lock file says: the pid it holds, nothing usable (`null`, empty or malformed), or no file
|
|
35549
|
+
* at all (`undefined`). A read that fails for any reason but a missing file propagates when
|
|
35550
|
+
* `strict`: a lock this process cannot read is not one it may reclaim. The release reads
|
|
35551
|
+
* non-strict, so cleanup never throws.
|
|
35552
|
+
*/
|
|
35553
|
+
function ownerOf(lockPath, strict = true) {
|
|
35554
|
+
let contents;
|
|
35555
|
+
try {
|
|
35556
|
+
contents = readFileSync(lockPath, "utf-8");
|
|
35557
|
+
} catch (error) {
|
|
35558
|
+
if (errorCode(error) === "ENOENT") return void 0;
|
|
35559
|
+
if (strict) throw error;
|
|
35560
|
+
return null;
|
|
35561
|
+
}
|
|
35562
|
+
const owner = Number(contents.trim());
|
|
35563
|
+
return Number.isInteger(owner) && owner > 0 ? owner : null;
|
|
35564
|
+
}
|
|
35565
|
+
/** A synchronous pause: the take is synchronous, and the wait is a few milliseconds at most. */
|
|
35566
|
+
function pause(ms) {
|
|
35567
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
35568
|
+
}
|
|
35569
|
+
/**
|
|
35570
|
+
* Publishes this pid at `lockPath` in one step: written to a sibling draft, then linked into
|
|
35571
|
+
* place, so the file is complete the moment it exists and a reader never sees it empty. True when
|
|
35572
|
+
* the link took; false when a lock already exists.
|
|
35573
|
+
*/
|
|
35574
|
+
function publish(lockPath) {
|
|
35575
|
+
const draft = `${lockPath}.${pid}.draft`;
|
|
35576
|
+
const fd = openSync(draft, "w");
|
|
35577
|
+
try {
|
|
35578
|
+
writeSync(fd, `${pid}\n`);
|
|
35579
|
+
} finally {
|
|
35580
|
+
closeSync(fd);
|
|
35581
|
+
}
|
|
35582
|
+
try {
|
|
35583
|
+
linkSync(draft, lockPath);
|
|
35584
|
+
return true;
|
|
35585
|
+
} catch (error) {
|
|
35586
|
+
if (errorCode(error) === "EEXIST") return false;
|
|
35587
|
+
throw error;
|
|
35588
|
+
} finally {
|
|
35589
|
+
rmSync(draft, { force: true });
|
|
35590
|
+
}
|
|
35591
|
+
}
|
|
35592
|
+
/**
|
|
35593
|
+
* The lock's owner, giving a file that is there but says nothing a few more reads: `publish` writes
|
|
35594
|
+
* the whole file before it appears, so an empty or malformed one is a leftover of some other
|
|
35595
|
+
* writer, and after the retries it counts as stale (`null`). `undefined` when the file is gone. A
|
|
35596
|
+
* file that cannot be read at all propagates.
|
|
35597
|
+
*/
|
|
35598
|
+
function settledOwner(lockPath) {
|
|
35599
|
+
for (let read = 0; read <= UNREADABLE_RETRIES; read += 1) {
|
|
35600
|
+
const owner = ownerOf(lockPath);
|
|
35601
|
+
if (owner !== null) return owner;
|
|
35602
|
+
pause(UNREADABLE_RETRY_MS);
|
|
35603
|
+
}
|
|
35604
|
+
return null;
|
|
35605
|
+
}
|
|
35606
|
+
/**
|
|
35607
|
+
* Claims the stale lock whose content was `expected` (a dead pid, or `null` for an unreadable
|
|
35608
|
+
* file): renames it to a sibling only this process names, which one claimant at most can win, and
|
|
35609
|
+
* removes the sibling when it still holds what was read. When it holds another pid, a live session
|
|
35610
|
+
* published between the read and the rename: the file goes back where it was, and the take starts
|
|
35611
|
+
* over. True when the stale lock is gone.
|
|
35612
|
+
*/
|
|
35613
|
+
function claimStale(lockPath, expected) {
|
|
35614
|
+
const claimed = `${lockPath}.${pid}.stale`;
|
|
35615
|
+
try {
|
|
35616
|
+
renameSync(lockPath, claimed);
|
|
35617
|
+
} catch (error) {
|
|
35618
|
+
if (errorCode(error) === "ENOENT") return true;
|
|
35619
|
+
throw error;
|
|
35620
|
+
}
|
|
35621
|
+
let found;
|
|
35622
|
+
try {
|
|
35623
|
+
found = ownerOf(claimed);
|
|
35624
|
+
} catch (error) {
|
|
35625
|
+
putBack(claimed, lockPath);
|
|
35626
|
+
throw error;
|
|
35627
|
+
}
|
|
35628
|
+
if (found === expected) {
|
|
35629
|
+
rmSync(claimed, { force: true });
|
|
35630
|
+
return true;
|
|
35631
|
+
}
|
|
35632
|
+
putBack(claimed, lockPath);
|
|
35633
|
+
return false;
|
|
35634
|
+
}
|
|
35635
|
+
/** Returns a claimed lock to `lockPath`, unless a lock was published there meanwhile, and drops the claim. */
|
|
35636
|
+
function putBack(claimed, lockPath) {
|
|
35637
|
+
try {
|
|
35638
|
+
linkSync(claimed, lockPath);
|
|
35639
|
+
} catch (error) {
|
|
35640
|
+
if (errorCode(error) !== "EEXIST") throw error;
|
|
35641
|
+
} finally {
|
|
35642
|
+
rmSync(claimed, { force: true });
|
|
35643
|
+
}
|
|
35644
|
+
}
|
|
35645
|
+
/**
|
|
35646
|
+
* Takes the session lock in `scratch`, the session root's `.vtex/storefront/`: a file holding this
|
|
35647
|
+
* process's pid. One `dev` composes there and runs the router on the configured port at a time: a
|
|
35648
|
+
* second session, in another process or a second provider instance in this one, would rewrite
|
|
35649
|
+
* `supergraph.graphql` with its Assets alone, which the first session's router polls and swaps in,
|
|
35650
|
+
* while its own router fails to bind. A live owner is a `SessionLockedError`; a dead owner's lock
|
|
35651
|
+
* is stale (a crashed session) and is taken over. Returns the release, which removes the file while
|
|
35652
|
+
* it still holds this pid.
|
|
35653
|
+
*
|
|
35654
|
+
* The file is the whole guarantee, so the take leaves no window: the pid is published complete or
|
|
35655
|
+
* not at all, a file that says nothing is read again before it counts as stale, and a stale file
|
|
35656
|
+
* is claimed by a rename only one claimant wins and verified before it is removed.
|
|
35657
|
+
*/
|
|
35658
|
+
function acquireSessionLock(scratch) {
|
|
35659
|
+
const lockPath = join(scratch, SESSION_LOCK_FILE);
|
|
35660
|
+
for (let attempt = 0; attempt < ATTEMPTS; attempt += 1) {
|
|
35661
|
+
if (publish(lockPath)) return () => {
|
|
35662
|
+
if (ownerOf(lockPath, false) === pid) rmSync(lockPath, { force: true });
|
|
35663
|
+
};
|
|
35664
|
+
const owner = settledOwner(lockPath);
|
|
35665
|
+
if (owner === void 0) continue;
|
|
35666
|
+
if (owner !== null && alive(owner)) throw new SessionLockedError(scratch, owner);
|
|
35667
|
+
claimStale(lockPath, owner);
|
|
35668
|
+
}
|
|
35669
|
+
throw new Error(`storefront dev: could not take the session lock at ${lockPath}: it kept changing hands.`);
|
|
35670
|
+
}
|
|
35671
|
+
//#endregion
|
|
35672
|
+
//#region src/cli/provider-dev.ts
|
|
35673
|
+
/** The runner's three levels into the session's `log`, for one Asset's lines. */
|
|
35674
|
+
function assetLog(session, target) {
|
|
35675
|
+
const line = (message) => session.log(target, message);
|
|
35676
|
+
return {
|
|
35677
|
+
info: line,
|
|
35678
|
+
warn: line,
|
|
35679
|
+
error: line
|
|
35680
|
+
};
|
|
35681
|
+
}
|
|
35682
|
+
function createState(session, ctx, env) {
|
|
35683
|
+
const state = {
|
|
35684
|
+
ctx,
|
|
35685
|
+
env,
|
|
35686
|
+
log: contextLog(ctx),
|
|
35687
|
+
report: {
|
|
35688
|
+
log: (target, message) => {
|
|
35689
|
+
if (!state.stopped) session.log(target, message);
|
|
35690
|
+
},
|
|
35691
|
+
status: (target, status, details) => {
|
|
35692
|
+
if (!state.stopped) session.status(target, status, details);
|
|
35693
|
+
},
|
|
35694
|
+
findings: (target, findings) => {
|
|
35695
|
+
if (!state.stopped) session.findings(target, findings);
|
|
35696
|
+
}
|
|
35697
|
+
},
|
|
35698
|
+
stopped: false,
|
|
35699
|
+
members: /* @__PURE__ */ new Map(),
|
|
35700
|
+
catalogs: /* @__PURE__ */ new Map(),
|
|
35701
|
+
sideFindings: {
|
|
35702
|
+
api: /* @__PURE__ */ new Map(),
|
|
35703
|
+
ui: /* @__PURE__ */ new Map()
|
|
35704
|
+
},
|
|
35705
|
+
reloadingApi: /* @__PURE__ */ new Set(),
|
|
35706
|
+
routerDown: false,
|
|
35707
|
+
declaredSecrets: /* @__PURE__ */ new Set()
|
|
35708
|
+
};
|
|
35709
|
+
return state;
|
|
35710
|
+
}
|
|
35711
|
+
/** Replaces the Asset's Findings with the union of both sides. */
|
|
35712
|
+
function setFindings(state, target, side, findings) {
|
|
35713
|
+
state.sideFindings[side].set(target, findings);
|
|
35714
|
+
state.report.findings(target, [...state.sideFindings.api.get(target) ?? [], ...state.sideFindings.ui.get(target) ?? []]);
|
|
35715
|
+
}
|
|
35716
|
+
/**
|
|
35717
|
+
* Adds to the API side's Findings instead of replacing them: a session compose failure arrives
|
|
35718
|
+
* after the load already reported the TypeScript and capability ones, and `validate` reports
|
|
35719
|
+
* them all together.
|
|
35720
|
+
*/
|
|
35721
|
+
function addApiFindings(state, target, findings) {
|
|
35722
|
+
setFindings(state, target, "api", [...state.sideFindings.api.get(target) ?? [], ...findings]);
|
|
35723
|
+
}
|
|
35724
|
+
/** The declaration entry as a Finding's `file`, or the Asset's `asset.yaml` when it lies outside the root. */
|
|
35725
|
+
function entryFileOf(ctx, asset) {
|
|
35726
|
+
return repoFile(ctx.root, apiDeclarationEntry(asset) ?? asset.dir) ?? asset.file;
|
|
35727
|
+
}
|
|
35728
|
+
function manifestFileOf(ctx, asset) {
|
|
35729
|
+
return repoFile(ctx.root, join(asset.dir, "package.json")) ?? asset.file;
|
|
35730
|
+
}
|
|
35731
|
+
/** The API side's Findings once its subgraph is up: the TypeScript one and the capability ones. */
|
|
35732
|
+
function subgraphFindings(ctx, asset, entryFile, subgraph) {
|
|
35733
|
+
return [...subgraph.typescript ? [typescriptUnsupported(manifestFileOf(ctx, asset), subgraph.typescript)] : [], ...subgraph.capability.map((finding) => capabilityFinding(ctx.root, entryFile, finding))];
|
|
35734
|
+
}
|
|
35735
|
+
/**
|
|
35736
|
+
* Starts the Asset's subgraph and gates it with the per-Asset composition check, so a composition
|
|
35737
|
+
* error is attributed to the Asset that caused it. Replaces the Asset's Findings. Returns the
|
|
35738
|
+
* running subgraph, or `undefined` after reporting `failed` with nothing listening.
|
|
35739
|
+
*/
|
|
35740
|
+
async function loadApiSide(state, asset) {
|
|
35741
|
+
const { ctx, report } = state;
|
|
35742
|
+
const { target } = asset;
|
|
35743
|
+
const entryFile = entryFileOf(ctx, asset);
|
|
35744
|
+
let subgraph;
|
|
35745
|
+
try {
|
|
35746
|
+
subgraph = await startSubgraph(asset.dir, {
|
|
35747
|
+
port: 0,
|
|
35748
|
+
variables: asset.env ?? ctx.env,
|
|
35749
|
+
log: assetLog(report, target)
|
|
35750
|
+
});
|
|
35751
|
+
} catch (error) {
|
|
35752
|
+
setFindings(state, target, "api", [declarationInvalid(entryFile, errorMessage(error))]);
|
|
35753
|
+
report.status(target, "failed");
|
|
35754
|
+
return;
|
|
35755
|
+
}
|
|
35756
|
+
for (const name of subgraph.secretNames) state.declaredSecrets.add(name);
|
|
35757
|
+
const findings = subgraphFindings(ctx, asset, entryFile, subgraph);
|
|
35758
|
+
const check = (0, import_compose.composeCheck)(coreFederationSdl(), subgraph.sdl, subgraph.name);
|
|
35759
|
+
if (check.success) {
|
|
35760
|
+
setFindings(state, target, "api", findings);
|
|
35761
|
+
return subgraph;
|
|
35762
|
+
}
|
|
35763
|
+
await subgraph.stop();
|
|
35764
|
+
setFindings(state, target, "api", [...findings, ...check.errors.map((error) => compositionFailed(entryFile, error.message))]);
|
|
35765
|
+
report.status(target, "failed");
|
|
35766
|
+
}
|
|
35767
|
+
/**
|
|
35768
|
+
* Builds the Asset's catalog where its export points, at boot and on every save, and replaces the
|
|
35769
|
+
* UI side's Findings: the export one (absent, or naming no file inside the Asset), and the addon's
|
|
35770
|
+
* error when the build threw. The build runs even without a usable export, into `dist/`, so both
|
|
35771
|
+
* errors arrive together. A failed build writes nothing, so the previous `catalog.json` stays as
|
|
35772
|
+
* it was and the Asset drops out of `catalogs`.
|
|
35773
|
+
*/
|
|
35774
|
+
async function buildUiSide(state, asset) {
|
|
35775
|
+
const { target } = asset;
|
|
35776
|
+
const manifestFile = manifestFileOf(state.ctx, asset);
|
|
35777
|
+
let findings = catalogExportFindings(catalogExport(asset), manifestFile);
|
|
35778
|
+
try {
|
|
35779
|
+
const { catalogPath } = await buildAssetCatalog(asset.dir, catalogOutDir(asset), "dev");
|
|
35780
|
+
state.catalogs.set(target, catalogPath);
|
|
35781
|
+
} catch (error) {
|
|
35782
|
+
state.catalogs.delete(target);
|
|
35783
|
+
findings = [...findings, catalogInvalid(manifestFile, repoRelativeMessage(state.ctx.root, errorMessage(error)))];
|
|
35784
|
+
}
|
|
35785
|
+
setFindings(state, target, "ui", findings);
|
|
35786
|
+
}
|
|
35787
|
+
/**
|
|
35788
|
+
* Boot: every Asset in call order, sequentially, the API side then the UI side, so two Assets'
|
|
35789
|
+
* `.vtex/storefront/load/` writes never interleave.
|
|
35790
|
+
*/
|
|
35791
|
+
async function bootAssets(state, assets, sides) {
|
|
35792
|
+
for (const asset of assets) {
|
|
35793
|
+
if (sides.api.includes(asset)) {
|
|
35794
|
+
const subgraph = await loadApiSide(state, asset);
|
|
35795
|
+
if (subgraph) state.members.set(asset.target, subgraph);
|
|
35796
|
+
}
|
|
35797
|
+
if (sides.ui.includes(asset)) await buildUiSide(state, asset);
|
|
35798
|
+
}
|
|
35799
|
+
}
|
|
35800
|
+
/**
|
|
35801
|
+
* Stops every member and empties `members`. Each stop runs whatever the others do (Apollo rejects
|
|
35802
|
+
* on a drain that fails), then the first failure propagates.
|
|
35803
|
+
*/
|
|
35804
|
+
async function stopMembers(members) {
|
|
35805
|
+
const stops = await Promise.allSettled([...members.values()].map((member) => member.stop()));
|
|
35806
|
+
members.clear();
|
|
35807
|
+
const failed = stops.find((stop) => stop.status === "rejected");
|
|
35808
|
+
if (failed) throw failed.reason;
|
|
35809
|
+
}
|
|
35810
|
+
/**
|
|
35811
|
+
* Composes the session from every member and returns the supergraph file. A compose failure after
|
|
35812
|
+
* every member passed alone is a cross-Asset conflict: it goes to every member, every member
|
|
35813
|
+
* stops, and the session composes core alone.
|
|
35814
|
+
*/
|
|
35815
|
+
async function composeBoot(state, apiAssets) {
|
|
35816
|
+
const { ctx, env, log, members, report } = state;
|
|
35817
|
+
try {
|
|
35818
|
+
return composeSession(ctx.root, env, [...members.values()], log).supergraphFile;
|
|
35819
|
+
} catch (error) {
|
|
35820
|
+
for (const asset of apiAssets) {
|
|
35821
|
+
if (!members.has(asset.target)) continue;
|
|
35822
|
+
addApiFindings(state, asset.target, [compositionFailed(entryFileOf(ctx, asset), errorMessage(error))]);
|
|
35823
|
+
report.status(asset.target, "failed");
|
|
35824
|
+
}
|
|
35825
|
+
await stopMembers(members);
|
|
35826
|
+
return composeSession(ctx.root, env, [], log).supergraphFile;
|
|
35827
|
+
}
|
|
35828
|
+
}
|
|
35829
|
+
/**
|
|
35830
|
+
* The router exited on its own after boot: the one URL every Asset reported is dead, so every
|
|
35831
|
+
* Asset is `failed`, with the cause in the session's log, and stays so until the session
|
|
35832
|
+
* restarts. The subgraphs keep running for `stop()` to take down.
|
|
35833
|
+
*/
|
|
35834
|
+
function routerExited(state, assets, exit) {
|
|
35835
|
+
if (state.stopped) return;
|
|
35836
|
+
state.routerDown = true;
|
|
35837
|
+
state.log.error(`storefront dev: the Hive Router exited ${describeRouterExit(exit)}; every Asset is failed until the session restarts.`);
|
|
35838
|
+
for (const asset of assets) state.report.status(asset.target, "failed");
|
|
35839
|
+
}
|
|
35840
|
+
/**
|
|
35841
|
+
* Launches the router over the composed supergraph and waits for it to answer. It can still fail
|
|
35842
|
+
* (binary download, spawn, an exit before it listens, a port that never answers); the boot rolls
|
|
35843
|
+
* every subgraph back then, as for any step that throws.
|
|
35844
|
+
*/
|
|
35845
|
+
function launchRouter(state, assets, supergraphFile) {
|
|
35846
|
+
const { ctx, env, log, members } = state;
|
|
35847
|
+
return startRouter(ctx.root, env, [...members.values()].map((member) => member.name), supergraphFile, {
|
|
35848
|
+
log,
|
|
35849
|
+
watchSupergraphSeconds: 1,
|
|
35850
|
+
onExit: (exit) => routerExited(state, assets, exit),
|
|
35851
|
+
withholdSecrets: state.declaredSecrets
|
|
35852
|
+
});
|
|
35853
|
+
}
|
|
35854
|
+
/**
|
|
35855
|
+
* One status from both sides: `ready` when every side the Asset has succeeded, with the router
|
|
35856
|
+
* URL, its subgraph for an API member and its catalog for a UI side; `failed` when any side
|
|
35857
|
+
* failed, or when the router is gone. A Finding alone never fails an Asset: an export-missing
|
|
35858
|
+
* Asset whose catalog built is `ready` with the Finding, the way a capability Finding leaves an
|
|
35859
|
+
* API Asset `ready`.
|
|
35860
|
+
*/
|
|
35861
|
+
function reportStatus(state, asset, routerUrl, sides) {
|
|
35862
|
+
const { target } = asset;
|
|
35863
|
+
const member = state.members.get(target);
|
|
35864
|
+
const catalog = state.catalogs.get(target);
|
|
35865
|
+
const apiDown = sides.api.includes(asset) && !member;
|
|
35866
|
+
const uiDown = sides.ui.includes(asset) && catalog === void 0;
|
|
35867
|
+
if (apiDown || uiDown || state.routerDown) {
|
|
35868
|
+
state.report.status(target, "failed");
|
|
35869
|
+
return;
|
|
35870
|
+
}
|
|
35871
|
+
state.report.status(target, "ready", {
|
|
35872
|
+
url: routerUrl,
|
|
35873
|
+
...member ? { subgraph: {
|
|
35874
|
+
name: member.name,
|
|
35875
|
+
url: member.url
|
|
35876
|
+
} } : {},
|
|
35877
|
+
...catalog ? { catalog } : {}
|
|
35878
|
+
});
|
|
35879
|
+
}
|
|
35880
|
+
/** Recomposes the current members; a failure keeps the last-good supergraph and reports to `ctx.log`. */
|
|
35881
|
+
function recompose(state) {
|
|
35882
|
+
try {
|
|
35883
|
+
composeSession(state.ctx.root, state.env, [...state.members.values()], state.log);
|
|
35884
|
+
} catch (error) {
|
|
35885
|
+
state.log.error(errorMessage(error));
|
|
35886
|
+
}
|
|
35887
|
+
}
|
|
35888
|
+
/**
|
|
35889
|
+
* Reloads one Asset's API side: stop its subgraph, load it again, recompose the session from the
|
|
35890
|
+
* current members. A fixed Asset rejoins; a broken one drops out, so the router serves an honest
|
|
35891
|
+
* graph instead of a dead URL. A session compose failure goes to the Asset that changed, which is
|
|
35892
|
+
* stopped and dropped, then one more compose without it. The target is in `reloadingApi` from the
|
|
35893
|
+
* old subgraph's stop to the new one's status, so a UI rebuild of a dual Asset that lands in
|
|
35894
|
+
* between never reports the gap as `failed`; the status reported here is the merged one.
|
|
35895
|
+
*/
|
|
35896
|
+
async function reloadApiSide(state, asset, routerUrl, sides) {
|
|
35897
|
+
if (state.stopped) return;
|
|
35898
|
+
const { ctx, env, log, members, report } = state;
|
|
35899
|
+
const { target } = asset;
|
|
35900
|
+
report.status(target, "starting");
|
|
35901
|
+
state.reloadingApi.add(target);
|
|
35902
|
+
try {
|
|
35903
|
+
const previous = members.get(target);
|
|
35904
|
+
members.delete(target);
|
|
35905
|
+
try {
|
|
35906
|
+
await previous?.stop();
|
|
35907
|
+
} catch (error) {
|
|
35908
|
+
log.error(errorMessage(error));
|
|
35909
|
+
}
|
|
35910
|
+
const next = await loadApiSide(state, asset);
|
|
35911
|
+
if (state.stopped) {
|
|
35912
|
+
await next?.stop();
|
|
35913
|
+
return;
|
|
35914
|
+
}
|
|
35915
|
+
if (!next) {
|
|
35916
|
+
recompose(state);
|
|
35917
|
+
return;
|
|
35918
|
+
}
|
|
35919
|
+
members.set(target, next);
|
|
35920
|
+
try {
|
|
35921
|
+
composeSession(ctx.root, env, [...members.values()], log);
|
|
35922
|
+
} catch (error) {
|
|
35923
|
+
members.delete(target);
|
|
35924
|
+
await next.stop();
|
|
35925
|
+
addApiFindings(state, target, [compositionFailed(entryFileOf(ctx, asset), errorMessage(error))]);
|
|
35926
|
+
report.status(target, "failed");
|
|
35927
|
+
recompose(state);
|
|
35928
|
+
return;
|
|
35929
|
+
}
|
|
35930
|
+
reportStatus(state, asset, routerUrl, sides);
|
|
35931
|
+
} finally {
|
|
35932
|
+
state.reloadingApi.delete(target);
|
|
35933
|
+
}
|
|
35934
|
+
}
|
|
35935
|
+
/**
|
|
35936
|
+
* Rebuilds one Asset's catalog on a save: `starting`, the build, then the merged status, unless the
|
|
35937
|
+
* Asset's API side is mid-reload, which reports the merged status itself once its subgraph is back.
|
|
35938
|
+
*/
|
|
35939
|
+
async function rebuildUiSide(state, asset, routerUrl, sides) {
|
|
35940
|
+
if (state.stopped) return;
|
|
35941
|
+
state.report.status(asset.target, "starting");
|
|
35942
|
+
await buildUiSide(state, asset);
|
|
35943
|
+
if (state.stopped || state.reloadingApi.has(asset.target)) return;
|
|
35944
|
+
reportStatus(state, asset, routerUrl, sides);
|
|
35945
|
+
}
|
|
35946
|
+
/**
|
|
35947
|
+
* One session-wide chain for the API side, so two Assets' reloads never compose with different
|
|
35948
|
+
* snapshots; a reload queued for a target that is already queued joins it, so a burst of saves is
|
|
35949
|
+
* one reload. One chain for the UI side: `buildPackageCatalog` is synchronous, so two rebuilds
|
|
35950
|
+
* never interleave. The tails are what `stop()` waits on when one is in flight.
|
|
35951
|
+
*/
|
|
35952
|
+
function createReloader(state, routerUrl, sides) {
|
|
35953
|
+
let reloading = Promise.resolve();
|
|
35954
|
+
const queued = /* @__PURE__ */ new Map();
|
|
35955
|
+
const reloadApi = (asset) => {
|
|
35956
|
+
const { target } = asset;
|
|
35957
|
+
const waiting = queued.get(target);
|
|
35958
|
+
if (waiting) return waiting;
|
|
35959
|
+
const run = reloading.then(() => {
|
|
35960
|
+
queued.delete(target);
|
|
35961
|
+
return reloadApiSide(state, asset, routerUrl, sides);
|
|
35962
|
+
});
|
|
35963
|
+
queued.set(target, run);
|
|
35964
|
+
reloading = run.catch(() => void 0);
|
|
35965
|
+
return run;
|
|
35966
|
+
};
|
|
35967
|
+
let catalogBuilds = Promise.resolve();
|
|
35968
|
+
const rebuildCatalog = (asset) => {
|
|
35969
|
+
const run = catalogBuilds.then(() => rebuildUiSide(state, asset, routerUrl, sides));
|
|
35970
|
+
catalogBuilds = run.catch(() => void 0);
|
|
35971
|
+
return run;
|
|
35972
|
+
};
|
|
35973
|
+
const reload = (target) => {
|
|
35974
|
+
const api = sides.api.find((candidate) => candidate.target === target);
|
|
35975
|
+
const ui = sides.ui.find((candidate) => candidate.target === target);
|
|
35976
|
+
if (api && ui) return Promise.all([reloadApi(api), rebuildCatalog(ui)]).then(() => void 0);
|
|
35977
|
+
if (api) return reloadApi(api);
|
|
35978
|
+
if (ui) return rebuildCatalog(ui);
|
|
35979
|
+
return Promise.resolve();
|
|
35980
|
+
};
|
|
35981
|
+
return {
|
|
35982
|
+
reloadSide: (asset, side) => side === "api" ? reloadApi(asset) : rebuildCatalog(asset),
|
|
35983
|
+
reload,
|
|
35984
|
+
settled: async () => {
|
|
35985
|
+
await reloading;
|
|
35986
|
+
await catalogBuilds;
|
|
35987
|
+
}
|
|
35988
|
+
};
|
|
35989
|
+
}
|
|
35990
|
+
/**
|
|
35991
|
+
* One `fs.watch` per distinct directory, each serving the `(Asset, side)` pairs rooted there: a
|
|
35992
|
+
* dual Asset whose declaration sits under `src/` gets one watcher for both sides, one whose `main`
|
|
35993
|
+
* sits elsewhere gets two, and a component save rebuilds the catalog alone.
|
|
35994
|
+
*/
|
|
35995
|
+
function subscriptionsOf(state, sides) {
|
|
35996
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
35997
|
+
const subscribe = (asset, side, watchDir) => {
|
|
35998
|
+
if (!existsSync(watchDir)) {
|
|
35999
|
+
state.report.log(asset.target, `storefront dev: ${watchDir} not found; watch mode is idle (no files to watch).`);
|
|
36000
|
+
return;
|
|
36001
|
+
}
|
|
36002
|
+
subscriptions.set(watchDir, [...subscriptions.get(watchDir) ?? [], {
|
|
36003
|
+
asset,
|
|
36004
|
+
side
|
|
36005
|
+
}]);
|
|
36006
|
+
};
|
|
36007
|
+
for (const asset of sides.api) subscribe(asset, "api", resolveWatchRoot(asset.dir));
|
|
36008
|
+
for (const asset of sides.ui) subscribe(asset, "ui", join(asset.dir, "src"));
|
|
36009
|
+
return subscriptions;
|
|
36010
|
+
}
|
|
36011
|
+
/** The event's file name as a string; some platforms omit it. */
|
|
36012
|
+
function eventFileName(filename) {
|
|
36013
|
+
return typeof filename === "string" ? filename : filename?.toString() ?? null;
|
|
36014
|
+
}
|
|
36015
|
+
/** What a catalog build writes into its folder: `catalog.json`, and the other two when not empty. */
|
|
36016
|
+
const CATALOG_FILES = [
|
|
36017
|
+
"catalog.json",
|
|
36018
|
+
"actions.json",
|
|
36019
|
+
"content-types.json"
|
|
36020
|
+
];
|
|
36021
|
+
/**
|
|
36022
|
+
* The files the session's catalog builds write, which an export may put inside a watched folder
|
|
36023
|
+
* (`./src/gen/catalog.json`, or anything but `dist/` under a root `main`). Each build rewrites
|
|
36024
|
+
* them, so following an event on one would rebuild on every rebuild. The files, not their folder,
|
|
36025
|
+
* which may hold source.
|
|
36026
|
+
*/
|
|
36027
|
+
function catalogWrites(uiAssets) {
|
|
36028
|
+
return new Set(uiAssets.flatMap((asset) => CATALOG_FILES.map((file) => join(catalogOutDir(asset), file))));
|
|
36029
|
+
}
|
|
36030
|
+
/**
|
|
36031
|
+
* The listener of one watched directory: a burst of events is one reload per `(Asset, side)` it
|
|
36032
|
+
* serves. `fs.watch` has no ignore option, so the session's own writes are filtered here.
|
|
36033
|
+
*/
|
|
36034
|
+
function debouncedReload(state, watchDir, subscribers, timers, reloader, ownWrites) {
|
|
36035
|
+
return (_event, filename) => {
|
|
36036
|
+
if (state.stopped) return;
|
|
36037
|
+
const name = eventFileName(filename);
|
|
36038
|
+
if (isIgnoredWatchPath(name)) return;
|
|
36039
|
+
if (name !== null && ownWrites.has(join(watchDir, name))) return;
|
|
36040
|
+
for (const { asset, side } of subscribers) {
|
|
36041
|
+
const key = `${side}:${asset.target}`;
|
|
36042
|
+
const timer = timers.get(key);
|
|
36043
|
+
if (timer) clearTimeout(timer);
|
|
36044
|
+
timers.set(key, setTimeout(() => {
|
|
36045
|
+
timers.delete(key);
|
|
36046
|
+
reloader.reloadSide(asset, side).catch((error) => {
|
|
36047
|
+
state.report.log(asset.target, `storefront dev: reload failed:\n${errorMessage(error)}`);
|
|
36048
|
+
});
|
|
36049
|
+
}, 150));
|
|
36050
|
+
}
|
|
36051
|
+
};
|
|
36052
|
+
}
|
|
36053
|
+
/**
|
|
36054
|
+
* Watches every subscribed directory, pushing each watcher into `watchers` as it opens, so a
|
|
36055
|
+
* `fs.watch` throw part way through leaves the ones already open where `stop()` closes them. An
|
|
36056
|
+
* unhandled `error` event would throw out of the event loop, in the host's process, so each
|
|
36057
|
+
* watcher reports one under the Assets it serves.
|
|
36058
|
+
*/
|
|
36059
|
+
function watchAll(state, subscriptions, timers, reloader, watchers, ownWrites) {
|
|
36060
|
+
for (const [watchDir, subscribers] of subscriptions) {
|
|
36061
|
+
const listener = debouncedReload(state, watchDir, subscribers, timers, reloader, ownWrites);
|
|
36062
|
+
const watcher = watch(watchDir, { recursive: true }, listener);
|
|
36063
|
+
watcher.on("error", (error) => {
|
|
36064
|
+
for (const { asset } of subscribers) state.report.log(asset.target, `storefront dev: watcher error on ${watchDir}: ${error.message}`);
|
|
36065
|
+
});
|
|
36066
|
+
watchers.push(watcher);
|
|
36067
|
+
}
|
|
36068
|
+
}
|
|
36069
|
+
/**
|
|
36070
|
+
* Everything `stop()` takes down, in order: watchers, timers, the router, every subgraph, then the
|
|
36071
|
+
* lock. Every subgraph is stopped before the first failed stop propagates (Apollo rejects on a
|
|
36072
|
+
* drain that fails), and the lock goes whatever happened above: a lock left behind by a stop that
|
|
36073
|
+
* threw would refuse the next `dev` in this process with the wrong cause, for as long as it lives.
|
|
36074
|
+
*/
|
|
36075
|
+
async function teardown(state, watchers, timers, router, reloader, releaseLock) {
|
|
36076
|
+
state.stopped = true;
|
|
36077
|
+
try {
|
|
36078
|
+
for (const watcher of watchers) watcher.close();
|
|
36079
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
36080
|
+
await router.stop();
|
|
36081
|
+
await reloader.settled();
|
|
36082
|
+
await stopMembers(state.members);
|
|
36083
|
+
} finally {
|
|
36084
|
+
releaseLock();
|
|
36085
|
+
}
|
|
36086
|
+
}
|
|
36087
|
+
async function startDevAdapter(assets, session, ctx) {
|
|
36088
|
+
const { VTEX_STOREFRONT_SUBGRAPH_PORT: subgraphPort, ...sessionEnv } = ctx.env;
|
|
36089
|
+
const env = loadRunnerEnv(sessionEnv);
|
|
36090
|
+
for (const line of renamedVariableWarnings(ctx.env)) ctx.log(line);
|
|
36091
|
+
if (subgraphPort !== void 0) ctx.log("storefront dev: VTEX_STOREFRONT_SUBGRAPH_PORT was ignored — the provider gives each Asset's subgraph its own port; only scripts/storefront-ext.mjs reads the variable.");
|
|
36092
|
+
const sides = {
|
|
36093
|
+
api: assets.filter((asset) => apiDeclarationEntry(asset) !== void 0),
|
|
36094
|
+
ui: assets.filter(uiSide)
|
|
36095
|
+
};
|
|
36096
|
+
if (sides.ui.length > 0) await loadCatalogBuilder("dev");
|
|
36097
|
+
const releaseLock = acquireSessionLock(scratchDir(ctx.root));
|
|
36098
|
+
const state = createState(session, ctx, env);
|
|
36099
|
+
let router;
|
|
36100
|
+
try {
|
|
36101
|
+
await bootAssets(state, assets, sides);
|
|
36102
|
+
router = await launchRouter(state, assets, await composeBoot(state, sides.api));
|
|
36103
|
+
} catch (error) {
|
|
36104
|
+
await stopMembers(state.members).catch(() => void 0);
|
|
36105
|
+
releaseLock();
|
|
36106
|
+
throw error;
|
|
36107
|
+
}
|
|
36108
|
+
const reloader = createReloader(state, router.routerUrl, sides);
|
|
36109
|
+
const watchers = [];
|
|
36110
|
+
const timers = /* @__PURE__ */ new Map();
|
|
36111
|
+
let stopping;
|
|
36112
|
+
const stop = () => {
|
|
36113
|
+
stopping ??= teardown(state, watchers, timers, router, reloader, releaseLock);
|
|
36114
|
+
return stopping;
|
|
36115
|
+
};
|
|
36116
|
+
try {
|
|
36117
|
+
watchAll(state, subscriptionsOf(state, sides), timers, reloader, watchers, catalogWrites(sides.ui));
|
|
36118
|
+
} catch (error) {
|
|
36119
|
+
await stop();
|
|
36120
|
+
throw error;
|
|
36121
|
+
}
|
|
36122
|
+
for (const asset of assets) if (state.members.has(asset.target) || !sides.api.includes(asset)) reportStatus(state, asset, router.routerUrl, sides);
|
|
36123
|
+
return {
|
|
36124
|
+
stop,
|
|
36125
|
+
reload: reloader.reload
|
|
36126
|
+
};
|
|
34856
36127
|
}
|
|
36128
|
+
//#endregion
|
|
36129
|
+
//#region src/cli/skills.ts
|
|
36130
|
+
/**
|
|
36131
|
+
* The provider's skills, written to contract version 0 (`@vtex/extension-cli-kit`): the Agent Skills
|
|
36132
|
+
* folders that teach a coding agent in an Account Repo to write storefront Assets. `vtex extension
|
|
36133
|
+
* skills`, and `add` once you agree, copy each folder to `.agents/skills/<name>/` and link
|
|
36134
|
+
* `.claude/skills/<name>` to it; nothing in a folder is code this package runs. `dir` is resolved
|
|
36135
|
+
* against the built entry, `dist/cli/index.js`, which tsdown bundles this module into, so `../../`
|
|
36136
|
+
* lands on the package root from source and from `dist/` alike.
|
|
36137
|
+
*/
|
|
36138
|
+
/** The one skill: `skills/storefront-asset/`, a `SKILL.md` and the references it points to. */
|
|
36139
|
+
const skills = [{
|
|
36140
|
+
name: "storefront-asset",
|
|
36141
|
+
dir: fileURLToPath(new URL("../../skills/storefront-asset/", import.meta.url))
|
|
36142
|
+
}];
|
|
36143
|
+
//#endregion
|
|
36144
|
+
//#region src/cli/templates.ts
|
|
36145
|
+
/**
|
|
36146
|
+
* The provider's templates, written to contract version 0 (`@vtex/extension-cli-kit`): what
|
|
36147
|
+
* `vtex extension add <ext>/<asset> --kind storefront` writes for a new storefront Asset. `files`
|
|
36148
|
+
* renders every file keyed by its path inside the Asset's folder and writes nothing; the core
|
|
36149
|
+
* writes `asset.yaml` and `package.json` itself and never overwrites a file. `packages` is what
|
|
36150
|
+
* `add` installs into the new Asset, one `pnpm add` per dependency field, and `packageJson` the
|
|
36151
|
+
* fields it merges into the `package.json` it bootstrapped.
|
|
36152
|
+
*/
|
|
34857
36153
|
/** Renders `src/index.ts`: a real `defineSubgraph` with the resolved `vendor`, a `Product.reviews` sample, and an in-memory resolver. */
|
|
34858
36154
|
function indexTemplate(vendor) {
|
|
34859
36155
|
return `import { defineSubgraph, gql } from '@vtex/faststore/graphql';
|
|
@@ -34892,7 +36188,7 @@ export default defineSubgraph({
|
|
|
34892
36188
|
});
|
|
34893
36189
|
`;
|
|
34894
36190
|
}
|
|
34895
|
-
/** Renders the
|
|
36191
|
+
/** Renders the API side's `tsconfig.json`: a known-good NodeNext config `build` type-checks the declaration against. */
|
|
34896
36192
|
function tsconfigTemplate() {
|
|
34897
36193
|
return `${JSON.stringify({
|
|
34898
36194
|
compilerOptions: {
|
|
@@ -34912,68 +36208,105 @@ function tsconfigTemplate() {
|
|
|
34912
36208
|
]
|
|
34913
36209
|
}, null, 2)}\n`;
|
|
34914
36210
|
}
|
|
34915
|
-
/**
|
|
34916
|
-
|
|
34917
|
-
|
|
34918
|
-
|
|
34919
|
-
|
|
34920
|
-
|
|
34921
|
-
|
|
34922
|
-
|
|
34923
|
-
|
|
34924
|
-
|
|
34925
|
-
|
|
34926
|
-
|
|
34927
|
-
|
|
34928
|
-
|
|
34929
|
-
|
|
36211
|
+
/**
|
|
36212
|
+
* Renders the UI side's `tsconfig.json`. The store compiles the components from source
|
|
36213
|
+
* (ADR-0026), so this config serves the editor and a later `typecheck` script only.
|
|
36214
|
+
*/
|
|
36215
|
+
function uiTsconfigTemplate() {
|
|
36216
|
+
return `${JSON.stringify({
|
|
36217
|
+
compilerOptions: {
|
|
36218
|
+
target: "ES2022",
|
|
36219
|
+
module: "ESNext",
|
|
36220
|
+
moduleResolution: "bundler",
|
|
36221
|
+
jsx: "react-jsx",
|
|
36222
|
+
lib: [
|
|
36223
|
+
"ES2022",
|
|
36224
|
+
"DOM",
|
|
36225
|
+
"DOM.Iterable"
|
|
36226
|
+
],
|
|
36227
|
+
strict: true,
|
|
36228
|
+
skipLibCheck: true,
|
|
36229
|
+
noEmit: true
|
|
36230
|
+
},
|
|
36231
|
+
include: ["src"]
|
|
36232
|
+
}, null, 2)}\n`;
|
|
36233
|
+
}
|
|
36234
|
+
/** Renders `src/components/HeroBanner.tsx`: a `defineComponent` sample whose catalog key is `<packageName>:HeroBanner`. */
|
|
36235
|
+
function componentTemplate(packageName) {
|
|
36236
|
+
return `import { defineComponent } from '@vtex/faststore';
|
|
36237
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
34930
36238
|
|
|
34931
|
-
|
|
34932
|
-
|
|
34933
|
-
|
|
36239
|
+
/** Props for {@link HeroBanner}. Extends all native \`<section>\` attributes. */
|
|
36240
|
+
export type HeroBannerProps = ComponentPropsWithRef<'section'> & {
|
|
36241
|
+
/** The headline text. */
|
|
36242
|
+
title?: string;
|
|
36243
|
+
};
|
|
34934
36244
|
|
|
34935
|
-
|
|
34936
|
-
|
|
34937
|
-
|
|
34938
|
-
|
|
34939
|
-
|
|
36245
|
+
export const HeroBannerDefinition = defineComponent<HeroBannerProps>({
|
|
36246
|
+
name: 'HeroBanner',
|
|
36247
|
+
importPath: '${packageName}',
|
|
36248
|
+
component: HeroBanner,
|
|
36249
|
+
editor: { label: 'Hero Banner', category: 'content' },
|
|
36250
|
+
schema: {
|
|
36251
|
+
title: { type: 'string', label: 'Title' },
|
|
36252
|
+
},
|
|
36253
|
+
});
|
|
34940
36254
|
|
|
34941
|
-
|
|
34942
|
-
|
|
34943
|
-
|
|
36255
|
+
export function HeroBanner({ title, className, ...props }: HeroBannerProps) {
|
|
36256
|
+
return (
|
|
36257
|
+
<section {...props} className={className} data-fs-hero-banner="">
|
|
36258
|
+
<h1>{title}</h1>
|
|
36259
|
+
</section>
|
|
36260
|
+
);
|
|
36261
|
+
}
|
|
36262
|
+
`;
|
|
36263
|
+
}
|
|
36264
|
+
/** Renders the UI side's `src/index.ts`: the barrel a store imports the components from. */
|
|
36265
|
+
function barrelTemplate() {
|
|
36266
|
+
return `// The barrel a UI Extension exports its components from: not a declaration, and never bundled as one.
|
|
36267
|
+
export type { HeroBannerProps } from './components/HeroBanner';
|
|
36268
|
+
export { HeroBanner, HeroBannerDefinition } from './components/HeroBanner';
|
|
34944
36269
|
`;
|
|
34945
36270
|
}
|
|
34946
|
-
/**
|
|
36271
|
+
/**
|
|
36272
|
+
* Renders `.gitignore`: the local artifacts a scaffold must never commit. Not `.env`: an Account
|
|
36273
|
+
* Repo tracks `.env` and ignores `.env*.local` at its root.
|
|
36274
|
+
*/
|
|
34947
36275
|
function gitignoreTemplate() {
|
|
34948
|
-
return `.
|
|
36276
|
+
return `.vtex/storefront/\ndist/\nnode_modules/\n`;
|
|
34949
36277
|
}
|
|
34950
|
-
/**
|
|
34951
|
-
|
|
34952
|
-
|
|
36278
|
+
/**
|
|
36279
|
+
* Renders `README.md`: what the scaffold is, where its settings live, and the lifecycle verbs.
|
|
36280
|
+
*/
|
|
36281
|
+
function readmeTemplate({ packageName }, side) {
|
|
36282
|
+
return `# ${packageName}
|
|
34953
36283
|
|
|
34954
|
-
|
|
34955
|
-
\`defineSubgraph\` (\`@vtex/faststore/graphql\`)
|
|
34956
|
-
\`
|
|
36284
|
+
${side === "api" ? `The API side of a storefront Asset: \`src/index.ts\` adds \`Product.reviews\` to the storefront
|
|
36285
|
+
graph through \`defineSubgraph\` (\`@vtex/faststore/graphql\`).` : `The UI side of a storefront Asset: \`src/components/HeroBanner.tsx\` is a \`defineComponent\`
|
|
36286
|
+
sample, and \`src/index.ts\` is the barrel a store imports it from.`}
|
|
34957
36287
|
|
|
34958
36288
|
## Setup
|
|
34959
36289
|
|
|
34960
|
-
|
|
34961
|
-
|
|
34962
|
-
# then fill in VTEX_ACCOUNT (and, if needed, VTEX_WORKSPACE / VTEX_TOKEN)
|
|
34963
|
-
\`\`\`
|
|
36290
|
+
\`VTEX_ACCOUNT\`, \`VTEX_WORKSPACE\` and \`VTEX_TOKEN\` go in the Account Repo root's \`.env\`
|
|
36291
|
+
(\`.env.local\` for the token), which \`vtex extension dev\` reads.
|
|
34964
36292
|
|
|
34965
36293
|
## Lifecycle
|
|
34966
36294
|
|
|
34967
36295
|
\`\`\`sh
|
|
34968
|
-
vtex
|
|
34969
|
-
vtex
|
|
34970
|
-
vtex
|
|
34971
|
-
node dist/server.mjs # run the built artifact as a standalone subgraph (production entrypoint)
|
|
34972
|
-
pnpm test # the Seam-A-style vitest check
|
|
36296
|
+
vtex extension dev # the local dev loop for the selected Assets
|
|
36297
|
+
vtex extension validate # the provider's checks, as Findings
|
|
36298
|
+
vtex extension build # both sides into .vtex/build/<ext>/<asset>/
|
|
34973
36299
|
\`\`\`
|
|
34974
36300
|
|
|
34975
|
-
|
|
34976
|
-
|
|
36301
|
+
${side === "api" ? `## Tests
|
|
36302
|
+
|
|
36303
|
+
\`pnpm test\` runs \`src/index.test.ts\` with \`vitest\`; \`pnpm typecheck\` runs \`tsc --noEmit\`.
|
|
36304
|
+
|
|
36305
|
+
Replace the in-memory fixture data in \`src/index.ts\` with a call to your own datasource.` : `## Components
|
|
36306
|
+
|
|
36307
|
+
\`vtex extension dev\` and \`vtex extension build\` write \`dist/catalog.json\`. \`package.json#exports\`
|
|
36308
|
+
points a store at \`src/index.ts\` and at that catalog. A store lists this package as a dependency
|
|
36309
|
+
and imports the components from \`src/\`.`}
|
|
34977
36310
|
`;
|
|
34978
36311
|
}
|
|
34979
36312
|
/**
|
|
@@ -35019,29 +36352,108 @@ describe('${vendor} extension', () => {
|
|
|
35019
36352
|
`;
|
|
35020
36353
|
}
|
|
35021
36354
|
/**
|
|
35022
|
-
* The
|
|
35023
|
-
* `
|
|
35024
|
-
*
|
|
35025
|
-
*
|
|
36355
|
+
* The two templates: `default`, the API side `add` writes without `--template`, and `ui`. The
|
|
36356
|
+
* storefront body is empty, so each `body` is `{}`. A `vendor` that violates the vendor grammar
|
|
36357
|
+
* is rejected up front, so a template never renders a declaration that would fail `validate`.
|
|
36358
|
+
*
|
|
36359
|
+
* `typescript` is pinned to the SDK's optional peer range: `validate` and `dev` load the compiler
|
|
36360
|
+
* from the Asset's devDependencies. `graphql` is pinned to the SDK's own range so the Asset holds
|
|
36361
|
+
* one copy: graphql-js rejects a schema built by another copy of itself, which is exactly what
|
|
36362
|
+
* the scaffolded test does. `react` is a peer because the store app renders the components.
|
|
35026
36363
|
*/
|
|
35027
36364
|
const templates = [{
|
|
35028
36365
|
name: "default",
|
|
35029
|
-
|
|
35030
|
-
|
|
35031
|
-
|
|
36366
|
+
summary: "The API side of a storefront Asset: adds Product.reviews to the storefront graph through defineSubgraph.",
|
|
36367
|
+
body: () => ({}),
|
|
36368
|
+
files(input) {
|
|
36369
|
+
assertValidVendor(input.vendor);
|
|
35032
36370
|
return {
|
|
35033
|
-
"package.json": packageJsonTemplate(name),
|
|
35034
36371
|
"tsconfig.json": tsconfigTemplate(),
|
|
35035
|
-
".env.example": envExampleTemplate(),
|
|
35036
36372
|
".gitignore": gitignoreTemplate(),
|
|
35037
|
-
"README.md": readmeTemplate(
|
|
35038
|
-
"src/index.ts": indexTemplate(vendor),
|
|
35039
|
-
"src/index.test.ts": testTemplate(vendor)
|
|
36373
|
+
"README.md": readmeTemplate(input, "api"),
|
|
36374
|
+
"src/index.ts": indexTemplate(input.vendor),
|
|
36375
|
+
"src/index.test.ts": testTemplate(input.vendor)
|
|
36376
|
+
};
|
|
36377
|
+
},
|
|
36378
|
+
packages: { devDependencies: {
|
|
36379
|
+
graphql: "^16.13.1",
|
|
36380
|
+
typescript: "^5.9.3",
|
|
36381
|
+
vitest: "^4.1.0"
|
|
36382
|
+
} },
|
|
36383
|
+
packageJson: {
|
|
36384
|
+
type: "module",
|
|
36385
|
+
scripts: {
|
|
36386
|
+
test: "vitest run",
|
|
36387
|
+
typecheck: "tsc --noEmit"
|
|
36388
|
+
}
|
|
36389
|
+
}
|
|
36390
|
+
}, {
|
|
36391
|
+
name: "ui",
|
|
36392
|
+
summary: "The UI side of a storefront Asset: a defineComponent sample a store reads through the catalog.",
|
|
36393
|
+
body: () => ({}),
|
|
36394
|
+
files(input) {
|
|
36395
|
+
assertValidVendor(input.vendor);
|
|
36396
|
+
return {
|
|
36397
|
+
"tsconfig.json": uiTsconfigTemplate(),
|
|
36398
|
+
".gitignore": gitignoreTemplate(),
|
|
36399
|
+
"README.md": readmeTemplate(input, "ui"),
|
|
36400
|
+
"src/index.ts": barrelTemplate(),
|
|
36401
|
+
"src/components/HeroBanner.tsx": componentTemplate(input.packageName)
|
|
35040
36402
|
};
|
|
36403
|
+
},
|
|
36404
|
+
packages: {
|
|
36405
|
+
devDependencies: {
|
|
36406
|
+
"@types/react": "^19.2.0",
|
|
36407
|
+
typescript: "^5.9.3"
|
|
36408
|
+
},
|
|
36409
|
+
peerDependencies: { react: "^19.0.0" }
|
|
36410
|
+
},
|
|
36411
|
+
packageJson: {
|
|
36412
|
+
type: "module",
|
|
36413
|
+
exports: {
|
|
36414
|
+
".": "./src/index.ts",
|
|
36415
|
+
"./catalog.json": "./dist/catalog.json"
|
|
36416
|
+
},
|
|
36417
|
+
scripts: { typecheck: "tsc --noEmit" }
|
|
35041
36418
|
}
|
|
35042
36419
|
}];
|
|
35043
36420
|
//#endregion
|
|
36421
|
+
//#region src/cli/provider.ts
|
|
36422
|
+
/** The one `asset.yaml` apiVersion `vtex extension add` writes. */
|
|
36423
|
+
const ASSET_API_VERSION = "assets.vtex.com/v1alpha1";
|
|
36424
|
+
/** Where the storefront kind is documented: the RFC that designs this provider. */
|
|
36425
|
+
const PROVIDER_DOCS = "https://github.com/vtex/faststore-ai-native/blob/main/docs/storefront-core/rfc-faststore-cli.md";
|
|
36426
|
+
/**
|
|
36427
|
+
* The storefront kind body is empty: a storefront `asset.yaml` carries the envelope and nothing
|
|
36428
|
+
* below it. The core compiles this with Ajv 2020, so `{}` is the one valid body.
|
|
36429
|
+
*/
|
|
36430
|
+
const STOREFRONT_BODY_SCHEMA = {
|
|
36431
|
+
type: "object",
|
|
36432
|
+
additionalProperties: false,
|
|
36433
|
+
properties: {}
|
|
36434
|
+
};
|
|
36435
|
+
/**
|
|
36436
|
+
* The storefront kind provider: the default export of `@vtex/faststore/cli`, written to contract
|
|
36437
|
+
* version 0 (`@vtex/extension-cli-kit`), which `vtex extension` loads from each storefront Asset's
|
|
36438
|
+
* own `@vtex/faststore` (ADR-0026). It wraps the runner's named exports.
|
|
36439
|
+
*
|
|
36440
|
+
* Like the rest of the runner, it prints nothing, reads no `process.env` and installs no signal
|
|
36441
|
+
* handler: `process-isolation.test.ts` walks this file with the others.
|
|
36442
|
+
*/
|
|
36443
|
+
const storefrontProvider = {
|
|
36444
|
+
contract: 0,
|
|
36445
|
+
kind: "storefront",
|
|
36446
|
+
apiVersions: [ASSET_API_VERSION],
|
|
36447
|
+
docs: PROVIDER_DOCS,
|
|
36448
|
+
schema: () => STOREFRONT_BODY_SCHEMA,
|
|
36449
|
+
templates,
|
|
36450
|
+
skills,
|
|
36451
|
+
validate: validateAssets,
|
|
36452
|
+
dev: startDevAdapter,
|
|
36453
|
+
build: buildAssets
|
|
36454
|
+
};
|
|
36455
|
+
//#endregion
|
|
35044
36456
|
var CORE_SUBGRAPH_NAME = import_subgraph_names.CORE_SUBGRAPH_NAME;
|
|
35045
|
-
export { CORE_SUBGRAPH_NAME, CompileError, ValidationGateError, buildExtension, buildSubgraph, formatFinding, formatValidationReport, lintExtension, printSubgraphSdl, resolveDeclarationEntry, runDev, runDevWatch, templates, validateExtension };
|
|
36457
|
+
export { CORE_SUBGRAPH_NAME, CompileError, TypeScriptUnavailableError, ValidationGateError, buildExtension, buildSubgraph, storefrontProvider as default, formatFinding, formatValidationReport, kinds, lintExtension, loadRunnerEnv, printSubgraphSdl, providerContractVersion, resolveDeclarationEntry, runDev, runDevWatch, skills, templates, validateExtension };
|
|
35046
36458
|
|
|
35047
36459
|
//# sourceMappingURL=index.js.map
|