@shopify/hydrogen 2026.10.0-preview.4 → 2026.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.mjs +23 -12
- package/dist/client/client.d.mts +18 -15
- package/dist/client/client.mjs +8 -3
- package/dist/client/client.mjs.map +1 -1
- package/dist/client/errors.d.mts +35 -13
- package/dist/client/errors.mjs +23 -13
- package/dist/client/errors.mjs.map +1 -1
- package/dist/client/types.d.mts +140 -90
- package/dist/core/analytics/cart-tracker.d.mts +19 -10
- package/dist/core/analytics/cart-tracker.mjs +18 -10
- package/dist/core/analytics/cart-tracker.mjs.map +1 -1
- package/dist/core/analytics/events.d.mts +17 -11
- package/dist/core/analytics/events.mjs +13 -4
- package/dist/core/analytics/events.mjs.map +1 -1
- package/dist/core/analytics/types.d.mts +207 -118
- package/dist/core/analytics/utils/flatten-connection.d.mts +11 -9
- package/dist/core/analytics/utils/flatten-connection.mjs +7 -6
- package/dist/core/analytics/utils/flatten-connection.mjs.map +1 -1
- package/dist/core/cache/fetch-with-cache.d.mts +24 -4
- package/dist/core/cache/fetch-with-cache.mjs +17 -2
- package/dist/core/cache/fetch-with-cache.mjs.map +1 -1
- package/dist/core/cache/key.d.mts +2 -0
- package/dist/core/cache/key.mjs.map +1 -1
- package/dist/core/cache/run-with-cache.d.mts +56 -8
- package/dist/core/cache/run-with-cache.mjs +17 -6
- package/dist/core/cache/run-with-cache.mjs.map +1 -1
- package/dist/core/cache/store.d.mts +8 -6
- package/dist/core/cache/store.mjs.map +1 -1
- package/dist/core/cache/strategies.d.mts +27 -2
- package/dist/core/cache/strategies.mjs +6 -0
- package/dist/core/cache/strategies.mjs.map +1 -1
- package/dist/core/cart/actions.d.mts +37 -30
- package/dist/core/cart/actions.mjs +21 -9
- package/dist/core/cart/actions.mjs.map +1 -1
- package/dist/core/cart/attach-quantity-input.d.mts +8 -7
- package/dist/core/cart/attach-quantity-input.mjs +8 -7
- package/dist/core/cart/attach-quantity-input.mjs.map +1 -1
- package/dist/core/cart/buyer-identity-sync.d.mts +7 -6
- package/dist/core/cart/buyer-identity-sync.mjs.map +1 -1
- package/dist/core/cart/cart.d.mts +59 -46
- package/dist/core/cart/cart.mjs +46 -29
- package/dist/core/cart/cart.mjs.map +1 -1
- package/dist/core/cart/cookie.d.mts +8 -4
- package/dist/core/cart/cookie.mjs +8 -4
- package/dist/core/cart/cookie.mjs.map +1 -1
- package/dist/core/cart/form.d.mts +34 -20
- package/dist/core/cart/form.mjs +6 -4
- package/dist/core/cart/form.mjs.map +1 -1
- package/dist/core/cart/get-cart.d.mts +27 -15
- package/dist/core/cart/get-cart.mjs +13 -8
- package/dist/core/cart/get-cart.mjs.map +1 -1
- package/dist/core/cart/quantity.d.mts +16 -5
- package/dist/core/cart/quantity.mjs +16 -5
- package/dist/core/cart/quantity.mjs.map +1 -1
- package/dist/core/cart/queries.d.mts +25 -19
- package/dist/core/cart/queries.mjs +15 -1
- package/dist/core/cart/queries.mjs.map +1 -1
- package/dist/core/cart/server-handlers.d.mts +82 -45
- package/dist/core/cart/server-handlers.mjs +4 -0
- package/dist/core/cart/server-handlers.mjs.map +1 -1
- package/dist/core/cart/state.d.mts +110 -64
- package/dist/core/cart/state.mjs +24 -6
- package/dist/core/cart/state.mjs.map +1 -1
- package/dist/core/collection/collection.d.mts +52 -53
- package/dist/core/collection/collection.mjs +5 -3
- package/dist/core/collection/collection.mjs.map +1 -1
- package/dist/core/collection/reconciler.d.mts +23 -16
- package/dist/core/collection/reconciler.mjs +8 -4
- package/dist/core/collection/reconciler.mjs.map +1 -1
- package/dist/core/collection/state.d.mts +31 -26
- package/dist/core/collection/state.mjs +4 -3
- package/dist/core/collection/state.mjs.map +1 -1
- package/dist/core/collection/url.d.mts +70 -29
- package/dist/core/collection/url.mjs +63 -25
- package/dist/core/collection/url.mjs.map +1 -1
- package/dist/core/constants.d.mts +1 -0
- package/dist/core/constants.mjs +1 -0
- package/dist/core/constants.mjs.map +1 -1
- package/dist/core/logging/logging.d.mts +14 -8
- package/dist/core/logging/logging.mjs +15 -10
- package/dist/core/logging/logging.mjs.map +1 -1
- package/dist/core/logging/types.d.mts +28 -11
- package/dist/core/logging/types.mjs.map +1 -1
- package/dist/core/money/format.d.mts +6 -4
- package/dist/core/money/format.mjs +19 -9
- package/dist/core/money/format.mjs.map +1 -1
- package/dist/core/money/types.d.mts +41 -25
- package/dist/core/predictive-search/form.d.mts +37 -7
- package/dist/core/predictive-search/form.mjs +21 -3
- package/dist/core/predictive-search/form.mjs.map +1 -1
- package/dist/core/predictive-search/queries.d.mts +18 -30
- package/dist/core/predictive-search/queries.mjs +7 -3
- package/dist/core/predictive-search/queries.mjs.map +1 -1
- package/dist/core/predictive-search/search.d.mts +25 -27
- package/dist/core/predictive-search/search.mjs +20 -19
- package/dist/core/predictive-search/search.mjs.map +1 -1
- package/dist/core/predictive-search/server-handlers.d.mts +25 -26
- package/dist/core/predictive-search/server-handlers.mjs +4 -0
- package/dist/core/predictive-search/server-handlers.mjs.map +1 -1
- package/dist/core/predictive-search/store.d.mts +65 -24
- package/dist/core/predictive-search/store.mjs +12 -1
- package/dist/core/predictive-search/store.mjs.map +1 -1
- package/dist/core/predictive-search/url.d.mts +36 -37
- package/dist/core/predictive-search/url.mjs +14 -6
- package/dist/core/predictive-search/url.mjs.map +1 -1
- package/dist/core/product/form.d.mts +63 -16
- package/dist/core/product/form.mjs +8 -2
- package/dist/core/product/form.mjs.map +1 -1
- package/dist/core/product/options.d.mts +8 -10
- package/dist/core/product/options.mjs +8 -10
- package/dist/core/product/options.mjs.map +1 -1
- package/dist/core/product/product-form.d.mts +77 -29
- package/dist/core/product/product-form.mjs +32 -10
- package/dist/core/product/product-form.mjs.map +1 -1
- package/dist/core/product/state.d.mts +55 -32
- package/dist/core/product/url.d.mts +19 -13
- package/dist/core/product/url.mjs +10 -10
- package/dist/core/product/url.mjs.map +1 -1
- package/dist/core/request-context.d.mts +77 -19
- package/dist/core/request-context.mjs +5 -1
- package/dist/core/request-context.mjs.map +1 -1
- package/dist/core/request-routing/handle-shopify-redirects.d.mts +12 -4
- package/dist/core/request-routing/handle-shopify-redirects.mjs +9 -2
- package/dist/core/request-routing/handle-shopify-redirects.mjs.map +1 -1
- package/dist/core/request-routing/handle-shopify-routes.d.mts +5 -5
- package/dist/core/request-routing/handle-shopify-routes.mjs +7 -5
- package/dist/core/request-routing/handle-shopify-routes.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/proxy.mjs +79 -43
- package/dist/core/request-routing/interceptors/proxy.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/ucp-profile-proxy.mjs +69 -0
- package/dist/core/request-routing/interceptors/ucp-profile-proxy.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/url-redirects.mjs +6 -1
- package/dist/core/request-routing/interceptors/url-redirects.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/well-known.mjs +1 -1
- package/dist/core/request-routing/interceptors/well-known.mjs.map +1 -1
- package/dist/core/request-routing/registered-routes.d.mts +12 -0
- package/dist/core/request-routing/registered-routes.mjs +12 -0
- package/dist/core/request-routing/registered-routes.mjs.map +1 -1
- package/dist/core/request-routing/route-types.d.mts +43 -18
- package/dist/core/shop-pay/shop-pay.d.mts +71 -35
- package/dist/core/shop-pay/shop-pay.mjs +36 -10
- package/dist/core/shop-pay/shop-pay.mjs.map +1 -1
- package/dist/core/shopify-scripts/index.d.mts +15 -5
- package/dist/core/shopify-scripts/index.mjs +15 -5
- package/dist/core/shopify-scripts/index.mjs.map +1 -1
- package/dist/core/shopify-scripts/initialize.d.mts +12 -4
- package/dist/core/shopify-scripts/initialize.mjs +12 -4
- package/dist/core/shopify-scripts/initialize.mjs.map +1 -1
- package/dist/core/shopify-scripts/types.d.mts +58 -25
- package/dist/core/standard-routes/build.d.mts +11 -12
- package/dist/core/standard-routes/build.mjs +11 -12
- package/dist/core/standard-routes/build.mjs.map +1 -1
- package/dist/core/standard-routes/types.d.mts +30 -24
- package/dist/core/url.mjs +2 -1
- package/dist/core/url.mjs.map +1 -1
- package/dist/customer-account/client.d.mts +36 -69
- package/dist/customer-account/client.mjs +7 -8
- package/dist/customer-account/client.mjs.map +1 -1
- package/dist/customer-account/errors.d.mts +45 -36
- package/dist/customer-account/errors.mjs +21 -36
- package/dist/customer-account/errors.mjs.map +1 -1
- package/dist/customer-account/graphql.d.mts +11 -23
- package/dist/customer-account/graphql.mjs +3 -9
- package/dist/customer-account/graphql.mjs.map +1 -1
- package/dist/customer-account/session.d.mts +130 -100
- package/dist/customer-account/session.mjs +32 -10
- package/dist/customer-account/session.mjs.map +1 -1
- package/dist/customer-account/type-resolver.d.mts +7 -8
- package/dist/development/client/client.d.mts +18 -15
- package/dist/development/client/client.mjs +8 -3
- package/dist/development/client/client.mjs.map +1 -1
- package/dist/development/client/errors.d.mts +35 -13
- package/dist/development/client/errors.mjs +23 -13
- package/dist/development/client/errors.mjs.map +1 -1
- package/dist/development/client/types.d.mts +140 -90
- package/dist/development/core/analytics/cart-tracker.d.mts +19 -10
- package/dist/development/core/analytics/cart-tracker.mjs +18 -10
- package/dist/development/core/analytics/cart-tracker.mjs.map +1 -1
- package/dist/development/core/analytics/events.d.mts +17 -11
- package/dist/development/core/analytics/events.mjs +13 -4
- package/dist/development/core/analytics/events.mjs.map +1 -1
- package/dist/development/core/analytics/types.d.mts +207 -118
- package/dist/development/core/analytics/utils/flatten-connection.d.mts +11 -9
- package/dist/development/core/analytics/utils/flatten-connection.mjs +7 -6
- package/dist/development/core/analytics/utils/flatten-connection.mjs.map +1 -1
- package/dist/development/core/cache/fetch-with-cache.d.mts +24 -4
- package/dist/development/core/cache/fetch-with-cache.mjs +17 -2
- package/dist/development/core/cache/fetch-with-cache.mjs.map +1 -1
- package/dist/development/core/cache/key.d.mts +2 -0
- package/dist/development/core/cache/key.mjs.map +1 -1
- package/dist/development/core/cache/run-with-cache.d.mts +56 -8
- package/dist/development/core/cache/run-with-cache.mjs +17 -6
- package/dist/development/core/cache/run-with-cache.mjs.map +1 -1
- package/dist/development/core/cache/store.d.mts +8 -6
- package/dist/development/core/cache/store.mjs.map +1 -1
- package/dist/development/core/cache/strategies.d.mts +27 -2
- package/dist/development/core/cache/strategies.mjs +6 -0
- package/dist/development/core/cache/strategies.mjs.map +1 -1
- package/dist/development/core/cart/actions.d.mts +37 -30
- package/dist/development/core/cart/actions.mjs +21 -9
- package/dist/development/core/cart/actions.mjs.map +1 -1
- package/dist/development/core/cart/attach-quantity-input.d.mts +8 -7
- package/dist/development/core/cart/attach-quantity-input.mjs +8 -7
- package/dist/development/core/cart/attach-quantity-input.mjs.map +1 -1
- package/dist/development/core/cart/buyer-identity-sync.d.mts +6 -5
- package/dist/development/core/cart/buyer-identity-sync.mjs.map +1 -1
- package/dist/development/core/cart/cart.d.mts +59 -46
- package/dist/development/core/cart/cart.mjs +46 -29
- package/dist/development/core/cart/cart.mjs.map +1 -1
- package/dist/development/core/cart/cookie.d.mts +8 -4
- package/dist/development/core/cart/cookie.mjs +8 -4
- package/dist/development/core/cart/cookie.mjs.map +1 -1
- package/dist/development/core/cart/form.d.mts +34 -20
- package/dist/development/core/cart/form.mjs +6 -4
- package/dist/development/core/cart/form.mjs.map +1 -1
- package/dist/development/core/cart/get-cart.d.mts +27 -15
- package/dist/development/core/cart/get-cart.mjs +13 -8
- package/dist/development/core/cart/get-cart.mjs.map +1 -1
- package/dist/development/core/cart/quantity.d.mts +16 -5
- package/dist/development/core/cart/quantity.mjs +16 -5
- package/dist/development/core/cart/quantity.mjs.map +1 -1
- package/dist/development/core/cart/queries.d.mts +25 -19
- package/dist/development/core/cart/queries.mjs +15 -1
- package/dist/development/core/cart/queries.mjs.map +1 -1
- package/dist/development/core/cart/server-handlers.d.mts +82 -45
- package/dist/development/core/cart/server-handlers.mjs +4 -0
- package/dist/development/core/cart/server-handlers.mjs.map +1 -1
- package/dist/development/core/cart/state.d.mts +110 -64
- package/dist/development/core/cart/state.mjs +24 -6
- package/dist/development/core/cart/state.mjs.map +1 -1
- package/dist/development/core/collection/collection.d.mts +52 -53
- package/dist/development/core/collection/collection.mjs +5 -3
- package/dist/development/core/collection/collection.mjs.map +1 -1
- package/dist/development/core/collection/reconciler.d.mts +23 -16
- package/dist/development/core/collection/reconciler.mjs +8 -4
- package/dist/development/core/collection/reconciler.mjs.map +1 -1
- package/dist/development/core/collection/state.d.mts +31 -26
- package/dist/development/core/collection/state.mjs +4 -3
- package/dist/development/core/collection/state.mjs.map +1 -1
- package/dist/development/core/collection/url.d.mts +70 -29
- package/dist/development/core/collection/url.mjs +63 -25
- package/dist/development/core/collection/url.mjs.map +1 -1
- package/dist/development/core/constants.mjs.map +1 -1
- package/dist/development/core/logging/logging.d.mts +14 -8
- package/dist/development/core/logging/logging.mjs +15 -10
- package/dist/development/core/logging/logging.mjs.map +1 -1
- package/dist/development/core/logging/types.d.mts +28 -11
- package/dist/development/core/logging/types.mjs.map +1 -1
- package/dist/development/core/money/format.d.mts +6 -4
- package/dist/development/core/money/format.mjs +19 -9
- package/dist/development/core/money/format.mjs.map +1 -1
- package/dist/development/core/money/types.d.mts +41 -25
- package/dist/development/core/predictive-search/form.d.mts +37 -7
- package/dist/development/core/predictive-search/form.mjs +21 -3
- package/dist/development/core/predictive-search/form.mjs.map +1 -1
- package/dist/development/core/predictive-search/queries.d.mts +18 -30
- package/dist/development/core/predictive-search/queries.mjs +7 -3
- package/dist/development/core/predictive-search/queries.mjs.map +1 -1
- package/dist/development/core/predictive-search/search.d.mts +25 -27
- package/dist/development/core/predictive-search/search.mjs +20 -19
- package/dist/development/core/predictive-search/search.mjs.map +1 -1
- package/dist/development/core/predictive-search/server-handlers.d.mts +25 -26
- package/dist/development/core/predictive-search/server-handlers.mjs +4 -0
- package/dist/development/core/predictive-search/server-handlers.mjs.map +1 -1
- package/dist/development/core/predictive-search/store.d.mts +65 -24
- package/dist/development/core/predictive-search/store.mjs +12 -1
- package/dist/development/core/predictive-search/store.mjs.map +1 -1
- package/dist/development/core/predictive-search/url.d.mts +36 -37
- package/dist/development/core/predictive-search/url.mjs +14 -6
- package/dist/development/core/predictive-search/url.mjs.map +1 -1
- package/dist/development/core/product/form.d.mts +63 -16
- package/dist/development/core/product/form.mjs +8 -2
- package/dist/development/core/product/form.mjs.map +1 -1
- package/dist/development/core/product/options.d.mts +8 -10
- package/dist/development/core/product/options.mjs +8 -10
- package/dist/development/core/product/options.mjs.map +1 -1
- package/dist/development/core/product/product-form.d.mts +77 -29
- package/dist/development/core/product/product-form.mjs +32 -10
- package/dist/development/core/product/product-form.mjs.map +1 -1
- package/dist/development/core/product/state.d.mts +55 -32
- package/dist/development/core/product/url.d.mts +19 -13
- package/dist/development/core/product/url.mjs +10 -10
- package/dist/development/core/product/url.mjs.map +1 -1
- package/dist/development/core/request-context.d.mts +77 -19
- package/dist/development/core/request-context.mjs +5 -1
- package/dist/development/core/request-context.mjs.map +1 -1
- package/dist/development/core/request-routing/handle-shopify-redirects.d.mts +12 -4
- package/dist/development/core/request-routing/handle-shopify-redirects.mjs +9 -2
- package/dist/development/core/request-routing/handle-shopify-redirects.mjs.map +1 -1
- package/dist/development/core/request-routing/handle-shopify-routes.development.d.mts +9 -1
- package/dist/development/core/request-routing/handle-shopify-routes.development.mjs +7 -0
- package/dist/development/core/request-routing/handle-shopify-routes.development.mjs.map +1 -1
- package/dist/development/core/request-routing/handle-shopify-routes.mjs +7 -5
- package/dist/development/core/request-routing/handle-shopify-routes.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/proxy.mjs +79 -43
- package/dist/development/core/request-routing/interceptors/proxy.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/ucp-profile-proxy.mjs +69 -0
- package/dist/development/core/request-routing/interceptors/ucp-profile-proxy.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/url-redirects.mjs +6 -1
- package/dist/development/core/request-routing/interceptors/url-redirects.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/well-known.mjs +1 -1
- package/dist/development/core/request-routing/interceptors/well-known.mjs.map +1 -1
- package/dist/development/core/request-routing/registered-routes.d.mts +12 -0
- package/dist/development/core/request-routing/registered-routes.mjs +12 -0
- package/dist/development/core/request-routing/registered-routes.mjs.map +1 -1
- package/dist/development/core/request-routing/route-types.d.mts +43 -18
- package/dist/development/core/shop-pay/shop-pay.d.mts +71 -35
- package/dist/development/core/shop-pay/shop-pay.mjs +36 -10
- package/dist/development/core/shop-pay/shop-pay.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/index.d.mts +15 -5
- package/dist/development/core/shopify-scripts/index.mjs +15 -5
- package/dist/development/core/shopify-scripts/index.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/initialize.d.mts +12 -4
- package/dist/development/core/shopify-scripts/initialize.mjs +12 -4
- package/dist/development/core/shopify-scripts/initialize.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/types.d.mts +58 -25
- package/dist/development/core/standard-routes/build.d.mts +11 -12
- package/dist/development/core/standard-routes/build.mjs +11 -12
- package/dist/development/core/standard-routes/build.mjs.map +1 -1
- package/dist/development/core/standard-routes/types.d.mts +30 -24
- package/dist/development/core/url.mjs +2 -1
- package/dist/development/core/url.mjs.map +1 -1
- package/dist/development/customer-account/session.d.mts +45 -54
- package/dist/development/customer-account/session.mjs +20 -4
- package/dist/development/customer-account/session.mjs.map +1 -1
- package/dist/development/globals.d.mts +20 -11
- package/dist/development/graphql/generated/storefront-api-types.d.mts +127 -1
- package/dist/development/graphql/graphql.d.mts +17 -16
- package/dist/development/graphql/graphql.mjs +7 -8
- package/dist/development/graphql/graphql.mjs.map +1 -1
- package/dist/development/graphql/type-resolver.d.mts +15 -8
- package/dist/development/react/cart.d.mts +78 -32
- package/dist/development/react/cart.mjs +56 -29
- package/dist/development/react/cart.mjs.map +1 -1
- package/dist/development/react/collection.d.mts +40 -22
- package/dist/development/react/collection.mjs +27 -7
- package/dist/development/react/collection.mjs.map +1 -1
- package/dist/development/react/predictive-search.d.mts +65 -50
- package/dist/development/react/predictive-search.mjs +23 -21
- package/dist/development/react/predictive-search.mjs.map +1 -1
- package/dist/development/react/product.d.mts +45 -16
- package/dist/development/react/product.mjs +13 -11
- package/dist/development/react/product.mjs.map +1 -1
- package/dist/development/react/shop-pay.d.mts +15 -0
- package/dist/development/react/shop-pay.mjs +9 -0
- package/dist/development/react/shop-pay.mjs.map +1 -1
- package/dist/development/react/shopify-scripts.d.mts +23 -2
- package/dist/development/react/shopify-scripts.mjs +9 -0
- package/dist/development/react/shopify-scripts.mjs.map +1 -1
- package/dist/development/vue/cart.d.mts +28 -23
- package/dist/development/vue/cart.mjs +42 -25
- package/dist/development/vue/cart.mjs.map +1 -1
- package/dist/development/vue/collection.d.mts +20 -12
- package/dist/development/vue/collection.mjs +15 -7
- package/dist/development/vue/collection.mjs.map +1 -1
- package/dist/development/vue/index.d.mts +2 -2
- package/dist/development/vue/index.mjs +2 -2
- package/dist/development/vue/predictive-search.d.mts +31 -36
- package/dist/development/vue/predictive-search.mjs +16 -22
- package/dist/development/vue/predictive-search.mjs.map +1 -1
- package/dist/development/vue/product.d.mts +16 -15
- package/dist/development/vue/product.mjs +11 -11
- package/dist/development/vue/product.mjs.map +1 -1
- package/dist/development/vue/shop-pay.d.mts +14 -17
- package/dist/development/vue/shop-pay.mjs +8 -0
- package/dist/development/vue/shop-pay.mjs.map +1 -1
- package/dist/development/vue/shopify-scripts.d.mts +11 -0
- package/dist/development/vue/shopify-scripts.mjs +6 -0
- package/dist/development/vue/shopify-scripts.mjs.map +1 -1
- package/dist/globals.d.mts +20 -11
- package/dist/graphql/generated/storefront-api-types.d.mts +127 -1
- package/dist/graphql/graphql.d.mts +17 -16
- package/dist/graphql/graphql.mjs +7 -8
- package/dist/graphql/graphql.mjs.map +1 -1
- package/dist/graphql/type-resolver.d.mts +15 -8
- package/dist/react/cart.d.mts +78 -32
- package/dist/react/cart.mjs +56 -29
- package/dist/react/cart.mjs.map +1 -1
- package/dist/react/collection.d.mts +40 -22
- package/dist/react/collection.mjs +27 -7
- package/dist/react/collection.mjs.map +1 -1
- package/dist/react/predictive-search.d.mts +65 -50
- package/dist/react/predictive-search.mjs +23 -21
- package/dist/react/predictive-search.mjs.map +1 -1
- package/dist/react/product.d.mts +45 -16
- package/dist/react/product.mjs +13 -11
- package/dist/react/product.mjs.map +1 -1
- package/dist/react/shop-pay.d.mts +15 -0
- package/dist/react/shop-pay.mjs +9 -0
- package/dist/react/shop-pay.mjs.map +1 -1
- package/dist/react/shopify-scripts.d.mts +23 -2
- package/dist/react/shopify-scripts.mjs +9 -0
- package/dist/react/shopify-scripts.mjs.map +1 -1
- package/dist/ts-plugin/index.cjs +6 -0
- package/dist/ts-plugin/index.cjs.map +1 -1
- package/dist/ts-plugin/index.d.cts +6 -0
- package/dist/vite/local-https.d.mts +51 -17
- package/dist/vite/local-https.mjs +26 -5
- package/dist/vite/local-https.mjs.map +1 -1
- package/dist/vue/cart.d.mts +28 -23
- package/dist/vue/cart.mjs +42 -25
- package/dist/vue/cart.mjs.map +1 -1
- package/dist/vue/collection.d.mts +20 -12
- package/dist/vue/collection.mjs +15 -7
- package/dist/vue/collection.mjs.map +1 -1
- package/dist/vue/index.d.mts +2 -2
- package/dist/vue/index.mjs +2 -2
- package/dist/vue/predictive-search.d.mts +31 -36
- package/dist/vue/predictive-search.mjs +16 -22
- package/dist/vue/predictive-search.mjs.map +1 -1
- package/dist/vue/product.d.mts +16 -15
- package/dist/vue/product.mjs +11 -11
- package/dist/vue/product.mjs.map +1 -1
- package/dist/vue/shop-pay.d.mts +14 -17
- package/dist/vue/shop-pay.mjs +8 -0
- package/dist/vue/shop-pay.mjs.map +1 -1
- package/dist/vue/shopify-scripts.d.mts +11 -0
- package/dist/vue/shopify-scripts.mjs +6 -0
- package/dist/vue/shopify-scripts.mjs.map +1 -1
- package/package.json +3 -1
- package/skills/hydrogen-analytics/SKILL.md +1 -1
- package/skills/hydrogen-cart-ui/SKILL.md +28 -2
- package/skills/hydrogen-collection-browser/SKILL.md +9 -1
- package/skills/hydrogen-collection-browser/references/nextjs.md +1 -1
- package/skills/hydrogen-collection-browser/references/nuxt.md +1 -1
- package/skills/hydrogen-collection-browser/references/react.md +151 -32
- package/skills/hydrogen-customer-account/SKILL.md +2 -0
- package/skills/hydrogen-request-handlers/SKILL.md +21 -11
- package/skills/hydrogen-setup/references/analytics.md +14 -8
- package/skills/hydrogen-setup/steps/1-inspect-app.md +2 -2
- package/skills/hydrogen-setup/steps/6-product-detail-page.md +21 -11
- package/skills/hydrogen-storefront-client/SKILL.md +1 -1
- package/skills/hydrogen-storefront-client/references/caching.md +2 -0
- package/skills/hydrogen-variant-form/SKILL.md +25 -7
- package/skills/hydrogen-variant-form/references/nextjs.md +39 -4
- package/skills/hydrogen-variant-form/references/nuxt.md +83 -42
- package/skills/hydrogen-variant-form/references/react.md +45 -9
|
@@ -1,107 +1,130 @@
|
|
|
1
1
|
//#region src/core/product/state.d.ts
|
|
2
|
-
/**
|
|
2
|
+
/** An amount and its currency, matching the Storefront API `MoneyV2` type. */
|
|
3
3
|
interface Money {
|
|
4
4
|
amount: string;
|
|
5
5
|
currencyCode: string;
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
7
|
+
/** Lowest and highest variant prices for a product, matching the Storefront API product price range. */
|
|
8
8
|
interface ProductPriceRange {
|
|
9
|
+
/** Lowest variant price. */
|
|
9
10
|
minVariantPrice: Money;
|
|
10
|
-
/**
|
|
11
|
+
/** Highest variant price. Leave the field out of queries that need only the lowest price. */
|
|
11
12
|
maxVariantPrice?: Money;
|
|
12
13
|
}
|
|
13
|
-
/** A single selected product option,
|
|
14
|
+
/** A single selected product option, such as `{ name: "Color", value: "Red" }`. */
|
|
14
15
|
interface SelectedOption {
|
|
16
|
+
/** Option name, such as "Color". */
|
|
15
17
|
name: string;
|
|
18
|
+
/** Value chosen for the option, such as "Red". */
|
|
16
19
|
value: string;
|
|
17
20
|
}
|
|
18
21
|
/**
|
|
19
|
-
*
|
|
22
|
+
* Variant fields that the product form needs from your Storefront API query.
|
|
20
23
|
*
|
|
21
|
-
*
|
|
22
|
-
* `ProductVariantInput` declares only the fields the form logic reads.
|
|
24
|
+
* Pass your full query type, which can include more fields.
|
|
23
25
|
*/
|
|
24
26
|
interface ProductVariantInput {
|
|
27
|
+
/** Variant ID that the product form adds to the cart. */
|
|
25
28
|
id: string;
|
|
29
|
+
/** Variant title. The cart shows the title on an added line before the server responds. */
|
|
26
30
|
title: string;
|
|
31
|
+
/** Whether the customer can buy the variant. Option value availability falls back to this field when your query doesn't select the encoded variant availability. */
|
|
27
32
|
availableForSale: boolean;
|
|
33
|
+
/** Option names and values that make up the variant. */
|
|
28
34
|
selectedOptions: SelectedOption[];
|
|
35
|
+
/** Variant price. The cart shows the price on an added line before the server responds. */
|
|
29
36
|
price: Money;
|
|
30
|
-
/**
|
|
37
|
+
/** Compare-at price, or `null` when the variant has none. */
|
|
31
38
|
compareAtPrice?: Money | null;
|
|
39
|
+
/** Variant image. The cart shows the image on an added line before the server responds. */
|
|
32
40
|
image?: unknown;
|
|
41
|
+
/** Handle and title of the variant's product. A handle that differs from the current product marks a variant of another product in a combined listing. */
|
|
33
42
|
product?: {
|
|
34
43
|
handle: string;
|
|
35
44
|
title?: string | null;
|
|
36
45
|
} | null;
|
|
46
|
+
/** Variant SKU. */
|
|
37
47
|
sku?: string | null;
|
|
38
48
|
}
|
|
39
|
-
/** A product option
|
|
49
|
+
/** A product option, such as "Size" or "Color", and its values. */
|
|
40
50
|
interface ProductOptionInput<TVariant extends ProductVariantInput = ProductVariantInput> {
|
|
51
|
+
/** Option name, such as "Size" or "Color". */
|
|
41
52
|
name: string;
|
|
53
|
+
/** Option values in the order that the Storefront API returns them. Keep that order, because the encoded variant fields refer to values by position. */
|
|
42
54
|
optionValues: Array<ProductOptionValueInput<TVariant>>;
|
|
43
55
|
}
|
|
44
|
-
/** A single option value
|
|
56
|
+
/** A single option value, such as "Small" or "Red", within a product option. */
|
|
45
57
|
interface ProductOptionValueInput<TVariant extends ProductVariantInput = ProductVariantInput> {
|
|
58
|
+
/** Option value name, such as "Small" or "Red". */
|
|
46
59
|
name: string;
|
|
47
|
-
/**
|
|
60
|
+
/** Variant that combines this value with the lowest-position values of every other option. Include this field in your query. The store uses the variant to resolve option values and to find values that belong to other products in a combined listing. */
|
|
48
61
|
firstSelectableVariant?: TVariant | null;
|
|
62
|
+
/** Swatch data from your query. The option value state returns the swatch unchanged. */
|
|
49
63
|
swatch?: unknown;
|
|
50
64
|
}
|
|
51
65
|
/**
|
|
52
|
-
*
|
|
66
|
+
* Product fields that the product form store needs from your Storefront API query.
|
|
53
67
|
*
|
|
54
|
-
*
|
|
55
|
-
* pass a wider query result — this interface declares only what the
|
|
56
|
-
* product form logic reads.
|
|
68
|
+
* The fields match the Storefront API product object. Pass your full query type, which can include more fields.
|
|
57
69
|
*/
|
|
58
70
|
interface ProductInput<TVariant extends ProductVariantInput = ProductVariantInput> {
|
|
71
|
+
/** Product ID. The product provider reloads the product into the store when this ID or the selected variant's ID changes. */
|
|
59
72
|
id: string;
|
|
73
|
+
/** Product title. */
|
|
60
74
|
title: string;
|
|
75
|
+
/** Product handle for option value links. Variants with a different handle belong to other products in a combined listing. */
|
|
61
76
|
handle: string;
|
|
77
|
+
/** Product vendor. */
|
|
62
78
|
vendor?: string | null;
|
|
79
|
+
/** Lowest and highest variant prices. Show the range until the selection resolves to a variant. */
|
|
63
80
|
priceRange?: ProductPriceRange;
|
|
64
|
-
/** When `true`, the
|
|
81
|
+
/** When `true`, the customer can't add the product to the cart without a selling plan. */
|
|
65
82
|
requiresSellingPlan?: boolean | null;
|
|
66
|
-
/** Encoded
|
|
83
|
+
/** Encoded list of the option value combinations that exist as variants. Pass the field unchanged from your query. */
|
|
67
84
|
encodedVariantExistence?: string | null;
|
|
68
|
-
/** Encoded
|
|
85
|
+
/** Encoded list of the variants that are available for sale. Pass the field unchanged from your query. */
|
|
69
86
|
encodedVariantAvailability?: string | null;
|
|
87
|
+
/** Product options and their values. The option state keeps this order. */
|
|
70
88
|
options: ProductOptionInput<TVariant>[];
|
|
71
|
-
/**
|
|
89
|
+
/** The variant that matches the query's `selectedOptions` argument, or else the first available variant, or else the first variant, which can be unavailable. The store starts with this variant selected. */
|
|
72
90
|
selectedOrFirstAvailableVariant: TVariant | null;
|
|
73
|
-
/** Variants
|
|
91
|
+
/** Variants that differ from the selected variant by one option value. The store resolves option values from these variants, and you don't need to query every variant. */
|
|
74
92
|
adjacentVariants: TVariant[];
|
|
75
93
|
}
|
|
76
|
-
/**
|
|
94
|
+
/** Variant type from your product query type. */
|
|
77
95
|
type ProductVariantFrom<TProduct extends ProductInput> = TProduct extends ProductInput<infer TVariant> ? TVariant : ProductVariantInput;
|
|
78
|
-
/**
|
|
96
|
+
/** Option value type from your product query type. */
|
|
79
97
|
type ProductOptionValueFrom<TProduct extends ProductInput> = TProduct["options"][number]["optionValues"][number];
|
|
80
98
|
/**
|
|
81
|
-
*
|
|
99
|
+
* State of one option value, such as "Red" under "Color", for the current selection.
|
|
82
100
|
*
|
|
83
|
-
*
|
|
84
|
-
* selection context, not static product data.
|
|
101
|
+
* Render the value's control and link from this state.
|
|
85
102
|
*/
|
|
86
103
|
interface VariantOptionValueState<TVariant extends ProductVariantInput = ProductVariantInput, TOptionValue extends ProductOptionValueInput = ProductOptionValueInput> {
|
|
104
|
+
/** Option value name, such as "Red". */
|
|
87
105
|
name: string;
|
|
106
|
+
/** Swatch data from the option value in your query. */
|
|
88
107
|
swatch?: TOptionValue["swatch"];
|
|
89
|
-
/** Whether
|
|
108
|
+
/** Whether the customer has selected the value. */
|
|
90
109
|
selected: boolean;
|
|
91
|
-
/** Whether a variant exists for the
|
|
110
|
+
/** Whether a variant exists for the selection that the value targets. Defaults to `true` when your query doesn't select the encoded variant existence field. */
|
|
92
111
|
exists: boolean;
|
|
93
|
-
/** Whether the
|
|
112
|
+
/** Whether the variant for the targeted selection is available for sale. Without the encoded variant availability in your query, the value comes from the loaded variant, or reads `false` when your query didn't load that variant. */
|
|
94
113
|
available: boolean;
|
|
95
|
-
/**
|
|
114
|
+
/** Variant for the targeted selection, or `null` when the selection is partial or your query didn't load that variant. */
|
|
96
115
|
variant: TVariant | null;
|
|
97
|
-
/**
|
|
116
|
+
/** Selection that the value targets. Build the option link from this selection. Selecting the value can produce a different selection. */
|
|
98
117
|
selectedOptions: SelectedOption[];
|
|
99
|
-
/** Product handle
|
|
118
|
+
/** Product handle for the option link. A value from another product in a combined listing uses that product's handle. Selected values use the current product's handle. */
|
|
100
119
|
handle: string;
|
|
101
120
|
}
|
|
102
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* State of one product option, such as "Color", and its values.
|
|
123
|
+
*/
|
|
103
124
|
interface VariantOptionState<TVariant extends ProductVariantInput = ProductVariantInput, TOptionValue extends ProductOptionValueInput = ProductOptionValueInput> {
|
|
125
|
+
/** Option name, such as "Color". */
|
|
104
126
|
name: string;
|
|
127
|
+
/** State of each value, in the product's value order. */
|
|
105
128
|
values: VariantOptionValueState<TVariant, TOptionValue>[];
|
|
106
129
|
}
|
|
107
130
|
//#endregion
|
|
@@ -2,25 +2,24 @@ import { SelectedOption } from "./state.mjs";
|
|
|
2
2
|
|
|
3
3
|
//#region src/core/product/url.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* How a product URL encodes the customer's variant selection.
|
|
6
6
|
*
|
|
7
|
-
* - `
|
|
8
|
-
* - `
|
|
7
|
+
* - `options`: one param per option, such as `?Color=Red&Size=M`.
|
|
8
|
+
* - `variant`: a single `?variant=<numeric id>` param that matches Liquid storefront URLs.
|
|
9
9
|
*/
|
|
10
10
|
type ProductSelectionLinkStyle = "options" | "variant";
|
|
11
11
|
/**
|
|
12
|
-
* Builds the search params for a product
|
|
12
|
+
* Builds the search params for a product URL that holds the customer's variant selection.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* reserved `variant` param and every param named in `optionNames` or `selectedOptions`,
|
|
16
|
-
* which are always removed before the new selection is written. This keeps stale selection
|
|
17
|
-
* state from leaking between navigations, including across combined-listing products.
|
|
14
|
+
* The function keeps unrelated params from the base params, such as `?ref=campaign`, and replaces any earlier selection. Pass the current product's option names to clear the current product's params when you link to another product in a combined listing.
|
|
18
15
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
16
|
+
* The default options style writes one param per option. The variant style writes a single numeric variant ID param for shareable links.
|
|
17
|
+
* The variant style falls back to option params when you pass no variant or when the variant ID isn't a numeric product variant GID.
|
|
18
|
+
* The `handleShopifyRoutes` handler redirects variant links to option param URLs.
|
|
19
|
+
*
|
|
20
|
+
* @param input The selected options, the current product's option names, and an optional variant, link style, and base params.
|
|
21
|
+
*
|
|
22
|
+
* @returns New search params that hold the kept base params and the selection.
|
|
24
23
|
*
|
|
25
24
|
* @example
|
|
26
25
|
* ```ts
|
|
@@ -32,6 +31,7 @@ type ProductSelectionLinkStyle = "options" | "variant";
|
|
|
32
31
|
* });
|
|
33
32
|
* const url = `/products/${handle}${searchParams.size ? `?${searchParams}` : ""}`;
|
|
34
33
|
* ```
|
|
34
|
+
* @publicDocs
|
|
35
35
|
*/
|
|
36
36
|
declare function buildProductSelectionSearchParams({
|
|
37
37
|
style,
|
|
@@ -40,11 +40,17 @@ declare function buildProductSelectionSearchParams({
|
|
|
40
40
|
optionNames,
|
|
41
41
|
base
|
|
42
42
|
}: {
|
|
43
|
+
/**
|
|
44
|
+
* Link style for the selection. Defaults to `"options"`.
|
|
45
|
+
*/
|
|
43
46
|
style?: ProductSelectionLinkStyle;
|
|
44
47
|
selectedOptions: readonly SelectedOption[];
|
|
45
48
|
variant?: {
|
|
46
49
|
id: string;
|
|
47
50
|
} | null;
|
|
51
|
+
/**
|
|
52
|
+
* Option names of the current product. The function clears the param for each name.
|
|
53
|
+
*/
|
|
48
54
|
optionNames: readonly string[];
|
|
49
55
|
base?: URLSearchParams;
|
|
50
56
|
}): URLSearchParams;
|
|
@@ -26,18 +26,17 @@ function getVariantSearchParamValue(variantId) {
|
|
|
26
26
|
return /^\d+$/.test(numericId) ? numericId : null;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
* Builds the search params for a product
|
|
29
|
+
* Builds the search params for a product URL that holds the customer's variant selection.
|
|
30
30
|
*
|
|
31
|
-
*
|
|
32
|
-
* reserved `variant` param and every param named in `optionNames` or `selectedOptions`,
|
|
33
|
-
* which are always removed before the new selection is written. This keeps stale selection
|
|
34
|
-
* state from leaking between navigations, including across combined-listing products.
|
|
31
|
+
* The function keeps unrelated params from the base params, such as `?ref=campaign`, and replaces any earlier selection. Pass the current product's option names to clear the current product's params when you link to another product in a combined listing.
|
|
35
32
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
33
|
+
* The default options style writes one param per option. The variant style writes a single numeric variant ID param for shareable links.
|
|
34
|
+
* The variant style falls back to option params when you pass no variant or when the variant ID isn't a numeric product variant GID.
|
|
35
|
+
* The `handleShopifyRoutes` handler redirects variant links to option param URLs.
|
|
36
|
+
*
|
|
37
|
+
* @param input The selected options, the current product's option names, and an optional variant, link style, and base params.
|
|
38
|
+
*
|
|
39
|
+
* @returns New search params that hold the kept base params and the selection.
|
|
41
40
|
*
|
|
42
41
|
* @example
|
|
43
42
|
* ```ts
|
|
@@ -49,6 +48,7 @@ function getVariantSearchParamValue(variantId) {
|
|
|
49
48
|
* });
|
|
50
49
|
* const url = `/products/${handle}${searchParams.size ? `?${searchParams}` : ""}`;
|
|
51
50
|
* ```
|
|
51
|
+
* @publicDocs
|
|
52
52
|
*/
|
|
53
53
|
function buildProductSelectionSearchParams({ style = "options", selectedOptions, variant, optionNames, base }) {
|
|
54
54
|
const searchParams = new URLSearchParams(base);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.mjs","names":[],"sources":["../../../../src/core/product/url.ts"],"sourcesContent":["import type { SelectedOption } from \"./state\";\n\nconst VARIANT_GID_PREFIX = \"gid://shopify/ProductVariant/\";\n\n/**\n * Reserved product-page search param carrying a numeric variant id (`?variant=41820371452004`),\n * matching Liquid storefront URLs. Never treated as a product option name.\n */\nexport const VARIANT_SEARCH_PARAM = \"variant\";\n\n/**\n * Converts a `?variant=` search param value into a variant id (gid).\n *\n * Anything that is not a plain positive integer of a plausible length is treated as absent\n * (`null`) so malformed links degrade to the default variant instead of producing invalid\n * API lookups or log spam.\n */\nexport function parseVariantSearchParam(value: string | null): string | null {\n if (!value || !/^\\d{1,30}$/.test(value)) return null;\n return `${VARIANT_GID_PREFIX}${value}`;\n}\n\n/**\n * Extracts the numeric `?variant=` search param value from a variant id (gid).\n * Returns `null` for ids that are not canonical `gid://shopify/ProductVariant/<numeric>` ids.\n */\nexport function getVariantSearchParamValue(variantId: string): string | null {\n if (!variantId.startsWith(VARIANT_GID_PREFIX)) return null;\n\n const numericId = variantId.slice(VARIANT_GID_PREFIX.length);\n return /^\\d+$/.test(numericId) ? numericId : null;\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"url.mjs","names":[],"sources":["../../../../src/core/product/url.ts"],"sourcesContent":["import type { SelectedOption } from \"./state\";\n\nconst VARIANT_GID_PREFIX = \"gid://shopify/ProductVariant/\";\n\n/**\n * Reserved product-page search param carrying a numeric variant id (`?variant=41820371452004`),\n * matching Liquid storefront URLs. Never treated as a product option name.\n */\nexport const VARIANT_SEARCH_PARAM = \"variant\";\n\n/**\n * Converts a `?variant=` search param value into a variant id (gid).\n *\n * Anything that is not a plain positive integer of a plausible length is treated as absent\n * (`null`) so malformed links degrade to the default variant instead of producing invalid\n * API lookups or log spam.\n */\nexport function parseVariantSearchParam(value: string | null): string | null {\n if (!value || !/^\\d{1,30}$/.test(value)) return null;\n return `${VARIANT_GID_PREFIX}${value}`;\n}\n\n/**\n * Extracts the numeric `?variant=` search param value from a variant id (gid).\n * Returns `null` for ids that are not canonical `gid://shopify/ProductVariant/<numeric>` ids.\n */\nexport function getVariantSearchParamValue(variantId: string): string | null {\n if (!variantId.startsWith(VARIANT_GID_PREFIX)) return null;\n\n const numericId = variantId.slice(VARIANT_GID_PREFIX.length);\n return /^\\d+$/.test(numericId) ? numericId : null;\n}\n\n/**\n * How a product URL encodes the customer's variant selection.\n *\n * - `options`: one param per option, such as `?Color=Red&Size=M`.\n * - `variant`: a single `?variant=<numeric id>` param that matches Liquid storefront URLs.\n */\nexport type ProductSelectionLinkStyle = \"options\" | \"variant\";\n\n/**\n * Builds the search params for a product URL that holds the customer's variant selection.\n *\n * The function keeps unrelated params from the base params, such as `?ref=campaign`, and replaces any earlier selection. Pass the current product's option names to clear the current product's params when you link to another product in a combined listing.\n *\n * The default options style writes one param per option. The variant style writes a single numeric variant ID param for shareable links.\n * The variant style falls back to option params when you pass no variant or when the variant ID isn't a numeric product variant GID.\n * The `handleShopifyRoutes` handler redirects variant links to option param URLs.\n *\n * @param input The selected options, the current product's option names, and an optional variant, link style, and base params.\n *\n * @returns New search params that hold the kept base params and the selection.\n *\n * @example\n * ```ts\n * const searchParams = buildProductSelectionSearchParams({\n * selectedOptions: result.selectedOptions,\n * variant: result.selectedVariant,\n * optionNames: product.options.map((option) => option.name),\n * base: new URLSearchParams(location.search),\n * });\n * const url = `/products/${handle}${searchParams.size ? `?${searchParams}` : \"\"}`;\n * ```\n * @publicDocs\n */\nexport function buildProductSelectionSearchParams({\n style = \"options\",\n selectedOptions,\n variant,\n optionNames,\n base,\n}: {\n /**\n * Link style for the selection. Defaults to `\"options\"`.\n */\n style?: ProductSelectionLinkStyle;\n selectedOptions: readonly SelectedOption[];\n variant?: { id: string } | null;\n /**\n * Option names of the current product. The function clears the param for each name.\n */\n optionNames: readonly string[];\n base?: URLSearchParams;\n}): URLSearchParams {\n const searchParams = new URLSearchParams(base);\n\n searchParams.delete(VARIANT_SEARCH_PARAM);\n for (const name of optionNames) searchParams.delete(name);\n for (const option of selectedOptions) searchParams.delete(option.name);\n\n const variantSearchParamValue =\n style === \"variant\" && variant ? getVariantSearchParamValue(variant.id) : null;\n\n if (variantSearchParamValue) {\n searchParams.set(VARIANT_SEARCH_PARAM, variantSearchParamValue);\n return searchParams;\n }\n\n for (const option of selectedOptions) {\n searchParams.set(option.name, option.value);\n }\n\n return searchParams;\n}\n"],"mappings":";AAEA,MAAM,qBAAqB;;;;;AAM3B,MAAa,uBAAuB;;;;;;;;AASpC,SAAgB,wBAAwB,OAAqC;AAC3E,KAAI,CAAC,SAAS,CAAC,aAAa,KAAK,MAAM,CAAE,QAAO;AAChD,QAAO,GAAG,qBAAqB;;;;;;AAOjC,SAAgB,2BAA2B,WAAkC;AAC3E,KAAI,CAAC,UAAU,WAAW,mBAAmB,CAAE,QAAO;CAEtD,MAAM,YAAY,UAAU,MAAM,GAA0B;AAC5D,QAAO,QAAQ,KAAK,UAAU,GAAG,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoC/C,SAAgB,kCAAkC,EAChD,QAAQ,WACR,iBACA,SACA,aACA,QAakB;CAClB,MAAM,eAAe,IAAI,gBAAgB,KAAK;AAE9C,cAAa,OAAO,qBAAqB;AACzC,MAAK,MAAM,QAAQ,YAAa,cAAa,OAAO,KAAK;AACzD,MAAK,MAAM,UAAU,gBAAiB,cAAa,OAAO,OAAO,KAAK;CAEtE,MAAM,0BACJ,UAAU,aAAa,UAAU,2BAA2B,QAAQ,GAAG,GAAG;AAE5E,KAAI,yBAAyB;AAC3B,eAAa,IAAI,sBAAsB,wBAAwB;AAC/D,SAAO;;AAGT,MAAK,MAAM,UAAU,gBACnB,cAAa,IAAI,OAAO,MAAM,OAAO,MAAM;AAG7C,QAAO"}
|
|
@@ -2,19 +2,31 @@ import { CountryCode, LanguageCode } from "../graphql/generated/customer-account
|
|
|
2
2
|
import { CountryCode as CountryCode$1, LanguageCode as LanguageCode$1 } from "../graphql/generated/storefront-api-types.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/core/request-context.d.ts
|
|
5
|
+
/** The incoming request's headers, plus its method, abort signal, and URL when the framework provides them. */
|
|
5
6
|
type StorefrontRequest = Pick<Request, "headers"> & Partial<Pick<Request, "method" | "signal" | "url">>;
|
|
7
|
+
/** A language code that both the Storefront API and the Customer Account API accept. */
|
|
6
8
|
type ShopifyLanguageCode = Extract<LanguageCode$1, LanguageCode>;
|
|
9
|
+
/** A country code that both the Storefront API and the Customer Account API accept. */
|
|
7
10
|
type ShopifyCountryCode = Extract<CountryCode$1, CountryCode>;
|
|
11
|
+
/** The language, country, and optional localized path prefix for a request. */
|
|
8
12
|
type I18nConfig = {
|
|
9
|
-
language: ShopifyLanguageCode;
|
|
10
|
-
country: ShopifyCountryCode;
|
|
13
|
+
/** The request's language. */language: ShopifyLanguageCode; /** The request's country. */
|
|
14
|
+
country: ShopifyCountryCode;
|
|
15
|
+
/**
|
|
16
|
+
* The URL prefix for localized routes, such as `/es-es`. Hydrogen adds the prefix to standard route redirects and product variant URLs.
|
|
17
|
+
*/
|
|
11
18
|
pathPrefix?: string;
|
|
12
19
|
};
|
|
20
|
+
/** The request's locale, with a path prefix that starts with `/` or is an empty string. */
|
|
13
21
|
type NormalizedI18nConfig<I18n extends I18nConfig = I18nConfig> = Omit<I18n, "pathPrefix"> & {
|
|
14
22
|
pathPrefix: string;
|
|
15
23
|
};
|
|
24
|
+
/** The incoming request and its locale. */
|
|
16
25
|
type ShopifyRequestContextInputBase<I18n extends I18nConfig = I18nConfig> = {
|
|
17
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The incoming request from your framework. When the framework exposes no request, such as during prerendering, pass `{ headers: new Headers() }`.
|
|
28
|
+
*/
|
|
29
|
+
request: StorefrontRequest; /** The request's language, country, and optional localized path prefix. */
|
|
18
30
|
i18n: I18n;
|
|
19
31
|
};
|
|
20
32
|
type ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> = ShopifyRequestContextInputBase<I18n> & {
|
|
@@ -23,54 +35,100 @@ type ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> = ShopifyR
|
|
|
23
35
|
type ShopifyRequestContextWithBuyerIpInput<I18n extends I18nConfig = I18nConfig> = ShopifyRequestContextInputBase<I18n> & {
|
|
24
36
|
buyerIp: string;
|
|
25
37
|
};
|
|
38
|
+
/** The fields and methods on every request context. */
|
|
26
39
|
type ShopifyRequestContextBase = {
|
|
27
40
|
/**
|
|
28
|
-
*
|
|
41
|
+
* A type-only marker. Create request contexts only with `createShopifyRequestContext`.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
readonly __hydrogenShopifyRequestContextBrand: never;
|
|
45
|
+
/**
|
|
46
|
+
* The incoming request's `Cookie` header. Hydrogen forwards the header to the Storefront API.
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
cookie?: string;
|
|
50
|
+
/**
|
|
51
|
+
* The customer's trusted IP address. Private Storefront API clients require it.
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
readonly buyerIp?: string;
|
|
55
|
+
/**
|
|
56
|
+
* The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one.
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
requestGroupId: string;
|
|
60
|
+
/**
|
|
61
|
+
* The incoming request's abort signal. Hydrogen's API clients stop their requests when the signal aborts.
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
signal?: AbortSignal;
|
|
65
|
+
/**
|
|
66
|
+
* The incoming request's URL.
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
url?: string;
|
|
70
|
+
/**
|
|
71
|
+
* The origin of the incoming request's URL. Hydrogen forwards the origin to the Storefront API.
|
|
29
72
|
* @internal
|
|
30
73
|
*/
|
|
31
|
-
readonly __hydrogenShopifyRequestContextBrand: never; /** @internal */
|
|
32
|
-
cookie?: string; /** @internal */
|
|
33
|
-
readonly buyerIp?: string; /** @internal */
|
|
34
|
-
requestGroupId: string; /** @internal */
|
|
35
|
-
signal?: AbortSignal; /** @internal */
|
|
36
|
-
url?: string; /** @internal */
|
|
37
74
|
storefrontOrigin?: string;
|
|
38
75
|
/**
|
|
39
|
-
*
|
|
76
|
+
* Adds the Hydrogen SDK headers and the customer's cookie, privacy, and origin headers to a
|
|
77
|
+
* Storefront API request. The Storefront API client and the API proxies call the method for you.
|
|
40
78
|
* @internal
|
|
41
79
|
*/
|
|
42
80
|
applyStorefrontRequestHeaders(headers: Headers): void;
|
|
43
81
|
/**
|
|
44
|
-
*
|
|
82
|
+
* Saves the cookies from the first Storefront API response that sets cookies. The Storefront API
|
|
83
|
+
* client calls the method for you, and `applyResponseHeaders()` can add the cookies to your response.
|
|
45
84
|
* @internal
|
|
46
85
|
*/
|
|
47
86
|
captureSubrequestHeaders(headers: Headers): void;
|
|
48
87
|
/**
|
|
49
|
-
*
|
|
88
|
+
* Saves the cookies from a proxied Storefront API response and removes the `set-cookie` header
|
|
89
|
+
* from that response. The API proxies in `handleShopifyRoutes` call the method for you.
|
|
50
90
|
* @internal
|
|
51
91
|
*/
|
|
52
92
|
consumeStorefrontResponseHeaders(headers: Headers): void;
|
|
53
93
|
/**
|
|
54
|
-
*
|
|
94
|
+
* Marks the final response as private and uncacheable because the response holds customer data.
|
|
95
|
+
* The Customer Account API client and the customer session call the method for you.
|
|
55
96
|
* @internal
|
|
56
97
|
*/
|
|
57
98
|
markResponseAsPersonalized(reason: string): void;
|
|
58
99
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
100
|
+
* Lets the final response set Shopify cookies before the customer has a Shopify session, and
|
|
101
|
+
* marks the response as private and uncacheable. The UCP MCP proxy calls the method for you.
|
|
61
102
|
* @internal
|
|
62
103
|
*/
|
|
63
|
-
markResponseAsSessionEstablishing(reason: string): void;
|
|
64
|
-
i18n: NormalizedI18nConfig; /**
|
|
65
|
-
getForwardedRequestHeaders(): Headers;
|
|
104
|
+
markResponseAsSessionEstablishing(reason: string): void; /** The request's language, country, and path prefix. The path prefix starts with `/` and has no trailing slash, or is an empty string when you don't set one. */
|
|
105
|
+
i18n: NormalizedI18nConfig; /** Returns the incoming request's headers with Shopify's request headers added. Pass the headers when you forward the request to a proxy or another origin. */
|
|
106
|
+
getForwardedRequestHeaders(): Headers;
|
|
107
|
+
/**
|
|
108
|
+
* Adds the headers that a Hydrogen storefront needs to the final response. Call the method on each response that your app builds. Responses from `handleShopifyRoutes` and `handleShopifyRedirects` already carry these headers.
|
|
109
|
+
*
|
|
110
|
+
* Append your committed session headers before you call the method. The method makes any response that sets a cookie private and uncacheable. The method also sets the `powered-by` header, makes responses that hold customer data private and uncacheable, and adds eligible Shopify cookies only to non-document responses for methods other than `GET` and `HEAD`.
|
|
111
|
+
*/
|
|
66
112
|
applyResponseHeaders(headers: Headers): void;
|
|
67
113
|
};
|
|
114
|
+
/** The context for one request. Pass the context to Hydrogen's API clients and route handlers for that request. */
|
|
68
115
|
type ShopifyRequestContext<I18n extends I18nConfig = I18nConfig> = ShopifyRequestContextBase & {
|
|
69
116
|
i18n: NormalizedI18nConfig<I18n>;
|
|
70
117
|
};
|
|
118
|
+
/** A request context that includes the customer's IP address. Private Storefront API clients require this context. */
|
|
71
119
|
type ShopifyRequestContextWithBuyerIp<I18n extends I18nConfig = I18nConfig> = ShopifyRequestContext<I18n> & {
|
|
72
120
|
readonly buyerIp: string;
|
|
73
121
|
};
|
|
122
|
+
/**
|
|
123
|
+
* Creates the context for one incoming request. Pass the same context to the Storefront API
|
|
124
|
+
* client, the Customer Account API client, and `handleShopifyRoutes`. The context forwards the
|
|
125
|
+
* customer's cookies, locale, and privacy signal to Shopify, and adds Shopify's cookies and cache
|
|
126
|
+
* headers to your response.
|
|
127
|
+
*
|
|
128
|
+
* Call `applyResponseHeaders()` on each response that your app builds. Pass `buyerIp` with the customer's trusted IP address when you use a private Storefront API client. The function throws when `i18n` lacks a country or language, and when `buyerIp` is an empty string.
|
|
129
|
+
*
|
|
130
|
+
* @publicDocs
|
|
131
|
+
*/
|
|
74
132
|
declare function createShopifyRequestContext<const I18n extends I18nConfig>(input: ShopifyRequestContextWithBuyerIpInput<I18n>): ShopifyRequestContextWithBuyerIp<I18n>;
|
|
75
133
|
declare function createShopifyRequestContext<const I18n extends I18nConfig>(input: ShopifyRequestContextInput<I18n>): ShopifyRequestContext<I18n>;
|
|
76
134
|
//#endregion
|
|
@@ -4,6 +4,10 @@ import { CONSENT_MANAGEMENT_HEADER, HYDROGEN_VERSION_HEADER, REQUEST_GROUP_ID_HE
|
|
|
4
4
|
|
|
5
5
|
//#region src/core/request-context.ts
|
|
6
6
|
const SHOPIFY_ESSENTIAL_COOKIE = "_shopify_essential";
|
|
7
|
+
/**
|
|
8
|
+
* @param input The incoming request, its locale, and the customer's IP address for private Storefront API clients.
|
|
9
|
+
* @returns The context to pass to Hydrogen's API clients and route handlers for this request.
|
|
10
|
+
*/
|
|
7
11
|
function createShopifyRequestContext(input) {
|
|
8
12
|
const { request } = input;
|
|
9
13
|
if (!input.i18n?.country || !input.i18n?.language) throw new Error("i18n with country and language is required for Shopify request contexts.");
|
|
@@ -80,7 +84,7 @@ function applyStorefrontRequestHeaders(context, headers) {
|
|
|
80
84
|
headers.set(SDK_VARIANT_HEADER, "hydrogen");
|
|
81
85
|
headers.set(SDK_VARIANT_SOURCE_HEADER, "kit");
|
|
82
86
|
headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);
|
|
83
|
-
headers.set(HYDROGEN_VERSION_HEADER, "2026.10.0
|
|
87
|
+
headers.set(HYDROGEN_VERSION_HEADER, "2026.10.0");
|
|
84
88
|
headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);
|
|
85
89
|
if (context.cookie) headers.set("cookie", context.cookie);
|
|
86
90
|
else headers.delete("cookie");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-context.mjs","names":[],"sources":["../../../src/core/request-context.ts"],"sourcesContent":["import type {\n CountryCode as CustomerAccountCountryCode,\n LanguageCode as CustomerAccountLanguageCode,\n} from \"../graphql/generated/customer-account-api-types\";\nimport type {\n CountryCode as StorefrontCountryCode,\n LanguageCode as StorefrontLanguageCode,\n} from \"../graphql/generated/storefront-api-types\";\nimport { STOREFRONT_API_VERSION } from \"./constants\";\nimport {\n applyPrivateResponseCacheHeaders,\n CONSENT_MANAGEMENT_HEADER,\n HYDROGEN_VERSION_HEADER,\n REQUEST_GROUP_ID_HEADER,\n SDK_VARIANT_HEADER,\n SDK_VARIANT_SOURCE_HEADER,\n SDK_VERSION_HEADER,\n SEC_GPC_HEADER,\n SHOPIFY_STOREFRONT_ORIGIN_HEADER,\n STOREFRONT_URL_HEADER,\n} from \"./headers\";\nimport { normalizePathPrefix } from \"./standard-routes/path\";\n\nconst SHOPIFY_ESSENTIAL_COOKIE = \"_shopify_essential\";\n\ntype StorefrontRequest = Pick<Request, \"headers\"> &\n Partial<Pick<Request, \"method\" | \"signal\" | \"url\">>;\n\nexport type ShopifyLanguageCode = Extract<StorefrontLanguageCode, CustomerAccountLanguageCode>;\nexport type ShopifyCountryCode = Extract<StorefrontCountryCode, CustomerAccountCountryCode>;\n\nexport type I18nConfig = {\n language: ShopifyLanguageCode;\n country: ShopifyCountryCode;\n /** Optional app route prefix for localized paths, for example \"/es-es\". */\n pathPrefix?: string;\n};\n\ntype NormalizedI18nConfig<I18n extends I18nConfig = I18nConfig> = Omit<I18n, \"pathPrefix\"> & {\n pathPrefix: string;\n};\n\ntype ShopifyRequestContextInputBase<I18n extends I18nConfig = I18nConfig> = {\n request: StorefrontRequest;\n i18n: I18n;\n};\n\ntype ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp?: never };\n\ntype ShopifyRequestContextWithBuyerIpInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp: string };\n\ntype ShopifyRequestContextBase = {\n // -- Private fields --\n /**\n * Compile-time brand so callers use createShopifyRequestContext().\n * @internal\n */\n readonly __hydrogenShopifyRequestContextBrand: never;\n /** @internal */\n cookie?: string;\n /** @internal */\n readonly buyerIp?: string;\n /** @internal */\n requestGroupId: string;\n /** @internal */\n signal?: AbortSignal;\n /** @internal */\n url?: string;\n /** @internal */\n storefrontOrigin?: string;\n /**\n * Apply request-scoped headers required by every Shopify storefront subrequest.\n * @internal\n */\n applyStorefrontRequestHeaders(headers: Headers): void;\n /**\n * Capture cookies from the first fresh storefront response that sets them for replay.\n * @internal\n */\n captureSubrequestHeaders(headers: Headers): void;\n /**\n * Consume storefront proxy cookies for gated replay onto the final app response.\n * @internal\n */\n consumeStorefrontResponseHeaders(headers: Headers): void;\n /**\n * Mark the final app response as influenced by private customer state.\n * @internal\n */\n markResponseAsPersonalized(reason: string): void;\n /**\n * Mark this request as a sanctioned session-establishing endpoint (like the\n * consent flow), permitting Shopify state to return even on a cold session.\n * @internal\n */\n markResponseAsSessionEstablishing(reason: string): void;\n\n // -- Public fields --\n i18n: NormalizedI18nConfig;\n /** Return incoming request headers plus request lifecycle headers for proxy/origin handoff. */\n getForwardedRequestHeaders(): Headers;\n /** Apply important response headers for the correct functioning of Hydrogen storefronts. */\n applyResponseHeaders(headers: Headers): void;\n};\n\nexport type ShopifyRequestContext<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextBase & {\n i18n: NormalizedI18nConfig<I18n>;\n };\n\nexport type ShopifyRequestContextWithBuyerIp<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContext<I18n> & { readonly buyerIp: string };\n\ntype Context<I18n extends I18nConfig = I18nConfig> = {\n cookie?: string;\n globalPrivacyControl?: string;\n buyerIp?: string;\n requestGroupId: string;\n signal?: AbortSignal;\n url?: string;\n storefrontOrigin?: string;\n i18n: NormalizedI18nConfig<I18n>;\n documentRequest?: boolean;\n};\n\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextWithBuyerIpInput<I18n>,\n): ShopifyRequestContextWithBuyerIp<I18n>;\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInput<I18n>,\n): ShopifyRequestContext<I18n>;\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInputBase<I18n> & { buyerIp?: string },\n): ShopifyRequestContext<I18n> {\n const { request } = input;\n\n if (!input.i18n?.country || !input.i18n?.language) {\n throw new Error(\"i18n with country and language is required for Shopify request contexts.\");\n }\n\n if (input.buyerIp !== undefined && !input.buyerIp) {\n throw new Error(\"buyerIp must be non-empty when provided\");\n }\n\n const requestMethod = request.method?.toUpperCase();\n const i18n = normalizeI18n(input.i18n);\n const cookieHeader = request.headers.get(\"cookie\") || undefined;\n const inboundCookies = parseCookieHeader(cookieHeader);\n const hasEssentialCookie = inboundCookies.has(SHOPIFY_ESSENTIAL_COOKIE);\n const isConsentManagementRequest = request.headers.get(CONSENT_MANAGEMENT_HEADER) === \"1\";\n const url = request.url ?? request.headers.get(STOREFRONT_URL_HEADER) ?? undefined;\n const storefrontOrigin = getUrlOrigin(url);\n const context = {\n ...(cookieHeader && { cookie: cookieHeader }),\n globalPrivacyControl: request.headers.get(SEC_GPC_HEADER) ?? undefined,\n i18n,\n ...(url && { url }),\n ...(storefrontOrigin && { storefrontOrigin }),\n ...(input.buyerIp && { buyerIp: input.buyerIp }),\n requestGroupId:\n request.headers.get(REQUEST_GROUP_ID_HEADER) ??\n request.headers.get(\"x-request-id\") ??\n request.headers.get(\"request-id\") ??\n crypto.randomUUID(),\n ...(isDocumentRequest(request) && { documentRequest: true }),\n ...(request.signal && { signal: request.signal }),\n } as Context<I18n>;\n\n let capturedCookies: string[] | undefined;\n let personalizedResponseReason: string | undefined;\n let sessionEstablishingReason: string | undefined;\n\n const captureSubrequestHeaders = (headers: Headers): void => {\n // Capture the first fresh response cookies to increase the\n // chance of returning them from the main server response. The main response\n // needs headers set at send time, while the body can stream later, so this\n // may not be used if subrequests finish after the main response is sent.\n const cookies = headers.getSetCookie();\n if (cookies.length > 0) capturedCookies ??= cookies;\n };\n\n return {\n ...context,\n getForwardedRequestHeaders() {\n const headers = new Headers(request.headers);\n applyStorefrontRequestHeaders(context, headers);\n if (context.url) headers.set(STOREFRONT_URL_HEADER, context.url);\n return headers;\n },\n applyStorefrontRequestHeaders(headers) {\n applyStorefrontRequestHeaders(context, headers);\n },\n captureSubrequestHeaders,\n consumeStorefrontResponseHeaders(headers) {\n captureSubrequestHeaders(headers);\n headers.delete(\"set-cookie\");\n },\n markResponseAsPersonalized(reason) {\n personalizedResponseReason ??= reason;\n },\n markResponseAsSessionEstablishing(reason) {\n sessionEstablishingReason ??= reason;\n },\n applyResponseHeaders(headers) {\n headers.set(\"powered-by\", \"Shopify, Hydrogen\");\n\n // Documents may be shared or streamed, so they must not carry buyer-specific state.\n const isDocumentResponse =\n context.documentRequest || (headers.get(\"content-type\")?.startsWith(\"text/html\") ?? false);\n\n // Keep GET and HEAD responses cacheable, and fail closed when the request method is unknown.\n // Cold sessions may only be established by the explicitly marked consent request.\n const mayReturnShopifyState =\n requestMethod !== undefined &&\n requestMethod !== \"GET\" &&\n requestMethod !== \"HEAD\" &&\n !isDocumentResponse &&\n (hasEssentialCookie ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined);\n\n // Replay cookies captured from fresh SFAPI and proxy responses when allowed.\n if (capturedCookies && mayReturnShopifyState) {\n const existingSetCookies = new Set(headers.getSetCookie());\n for (const value of capturedCookies) {\n if (existingSetCookies.has(value)) continue;\n headers.append(\"set-cookie\", value);\n existingSetCookies.add(value);\n }\n }\n\n // Consent/session responses can contain private state in the body without setting cookies.\n if (\n personalizedResponseReason ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined ||\n headers.has(\"set-cookie\")\n ) {\n applyPrivateResponseCacheHeaders(headers);\n }\n },\n } as ShopifyRequestContext<I18n>;\n}\n\nfunction normalizeI18n<I18n extends I18nConfig>(i18n: I18n): NormalizedI18nConfig<I18n> {\n return {\n ...i18n,\n pathPrefix: normalizePathPrefix(i18n.pathPrefix),\n } as NormalizedI18nConfig<I18n>;\n}\n\nfunction applyStorefrontRequestHeaders(context: Context, headers: Headers): void {\n headers.set(SDK_VARIANT_HEADER, \"hydrogen\");\n headers.set(SDK_VARIANT_SOURCE_HEADER, \"kit\");\n headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);\n headers.set(HYDROGEN_VERSION_HEADER, __HYDROGEN_VERSION__);\n headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);\n\n if (context.cookie) headers.set(\"cookie\", context.cookie);\n else headers.delete(\"cookie\");\n if (context.globalPrivacyControl !== undefined) {\n headers.set(SEC_GPC_HEADER, context.globalPrivacyControl);\n } else headers.delete(SEC_GPC_HEADER);\n if (context.storefrontOrigin) {\n headers.set(SHOPIFY_STOREFRONT_ORIGIN_HEADER, context.storefrontOrigin);\n } else headers.delete(SHOPIFY_STOREFRONT_ORIGIN_HEADER);\n}\n\nfunction getUrlOrigin(url: string | undefined): string | undefined {\n if (!url) return undefined;\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nfunction isDocumentRequest(request: StorefrontRequest): boolean {\n if (request.method && request.method !== \"GET\" && request.method !== \"HEAD\") return false;\n\n const destination = request.headers.get(\"sec-fetch-dest\");\n if (destination === \"document\") return true;\n\n return request.headers.get(\"accept\")?.includes(\"text/html\") ?? false;\n}\n\nfunction parseCookieHeader(cookieHeader: string | undefined): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n\n for (const cookie of cookieHeader.split(\";\")) {\n const separator = cookie.indexOf(\"=\");\n if (separator < 0) continue;\n\n const name = cookie.slice(0, separator).trim();\n if (!cookies.has(name)) cookies.set(name, cookie.slice(separator + 1));\n }\n\n return cookies;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"request-context.mjs","names":[],"sources":["../../../src/core/request-context.ts"],"sourcesContent":["import type {\n CountryCode as CustomerAccountCountryCode,\n LanguageCode as CustomerAccountLanguageCode,\n} from \"../graphql/generated/customer-account-api-types\";\nimport type {\n CountryCode as StorefrontCountryCode,\n LanguageCode as StorefrontLanguageCode,\n} from \"../graphql/generated/storefront-api-types\";\nimport { STOREFRONT_API_VERSION } from \"./constants\";\nimport {\n applyPrivateResponseCacheHeaders,\n CONSENT_MANAGEMENT_HEADER,\n HYDROGEN_VERSION_HEADER,\n REQUEST_GROUP_ID_HEADER,\n SDK_VARIANT_HEADER,\n SDK_VARIANT_SOURCE_HEADER,\n SDK_VERSION_HEADER,\n SEC_GPC_HEADER,\n SHOPIFY_STOREFRONT_ORIGIN_HEADER,\n STOREFRONT_URL_HEADER,\n} from \"./headers\";\nimport { normalizePathPrefix } from \"./standard-routes/path\";\n\nconst SHOPIFY_ESSENTIAL_COOKIE = \"_shopify_essential\";\n\n/** The incoming request's headers, plus its method, abort signal, and URL when the framework provides them. */\ntype StorefrontRequest = Pick<Request, \"headers\"> &\n Partial<Pick<Request, \"method\" | \"signal\" | \"url\">>;\n\n/** A language code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyLanguageCode = Extract<StorefrontLanguageCode, CustomerAccountLanguageCode>;\n/** A country code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyCountryCode = Extract<StorefrontCountryCode, CustomerAccountCountryCode>;\n\n/** The language, country, and optional localized path prefix for a request. */\nexport type I18nConfig = {\n /** The request's language. */\n language: ShopifyLanguageCode;\n /** The request's country. */\n country: ShopifyCountryCode;\n /**\n * The URL prefix for localized routes, such as `/es-es`. Hydrogen adds the prefix to standard route redirects and product variant URLs.\n */\n pathPrefix?: string;\n};\n\n/** The request's locale, with a path prefix that starts with `/` or is an empty string. */\ntype NormalizedI18nConfig<I18n extends I18nConfig = I18nConfig> = Omit<I18n, \"pathPrefix\"> & {\n pathPrefix: string;\n};\n\n/** The incoming request and its locale. */\ntype ShopifyRequestContextInputBase<I18n extends I18nConfig = I18nConfig> = {\n /**\n * The incoming request from your framework. When the framework exposes no request, such as during prerendering, pass `{ headers: new Headers() }`.\n */\n request: StorefrontRequest;\n /** The request's language, country, and optional localized path prefix. */\n i18n: I18n;\n};\n\ntype ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp?: never };\n\ntype ShopifyRequestContextWithBuyerIpInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp: string };\n\n/** The fields and methods on every request context. */\ntype ShopifyRequestContextBase = {\n // -- Private fields --\n /**\n * A type-only marker. Create request contexts only with `createShopifyRequestContext`.\n * @internal\n */\n readonly __hydrogenShopifyRequestContextBrand: never;\n /**\n * The incoming request's `Cookie` header. Hydrogen forwards the header to the Storefront API.\n * @internal\n */\n cookie?: string;\n /**\n * The customer's trusted IP address. Private Storefront API clients require it.\n * @internal\n */\n readonly buyerIp?: string;\n /**\n * The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one.\n * @internal\n */\n requestGroupId: string;\n /**\n * The incoming request's abort signal. Hydrogen's API clients stop their requests when the signal aborts.\n * @internal\n */\n signal?: AbortSignal;\n /**\n * The incoming request's URL.\n * @internal\n */\n url?: string;\n /**\n * The origin of the incoming request's URL. Hydrogen forwards the origin to the Storefront API.\n * @internal\n */\n storefrontOrigin?: string;\n /**\n * Adds the Hydrogen SDK headers and the customer's cookie, privacy, and origin headers to a\n * Storefront API request. The Storefront API client and the API proxies call the method for you.\n * @internal\n */\n applyStorefrontRequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from the first Storefront API response that sets cookies. The Storefront API\n * client calls the method for you, and `applyResponseHeaders()` can add the cookies to your response.\n * @internal\n */\n captureSubrequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from a proxied Storefront API response and removes the `set-cookie` header\n * from that response. The API proxies in `handleShopifyRoutes` call the method for you.\n * @internal\n */\n consumeStorefrontResponseHeaders(headers: Headers): void;\n /**\n * Marks the final response as private and uncacheable because the response holds customer data.\n * The Customer Account API client and the customer session call the method for you.\n * @internal\n */\n markResponseAsPersonalized(reason: string): void;\n /**\n * Lets the final response set Shopify cookies before the customer has a Shopify session, and\n * marks the response as private and uncacheable. The UCP MCP proxy calls the method for you.\n * @internal\n */\n markResponseAsSessionEstablishing(reason: string): void;\n\n // -- Public fields --\n /** The request's language, country, and path prefix. The path prefix starts with `/` and has no trailing slash, or is an empty string when you don't set one. */\n i18n: NormalizedI18nConfig;\n /** Returns the incoming request's headers with Shopify's request headers added. Pass the headers when you forward the request to a proxy or another origin. */\n getForwardedRequestHeaders(): Headers;\n /**\n * Adds the headers that a Hydrogen storefront needs to the final response. Call the method on each response that your app builds. Responses from `handleShopifyRoutes` and `handleShopifyRedirects` already carry these headers.\n *\n * Append your committed session headers before you call the method. The method makes any response that sets a cookie private and uncacheable. The method also sets the `powered-by` header, makes responses that hold customer data private and uncacheable, and adds eligible Shopify cookies only to non-document responses for methods other than `GET` and `HEAD`.\n */\n applyResponseHeaders(headers: Headers): void;\n};\n\n/** The context for one request. Pass the context to Hydrogen's API clients and route handlers for that request. */\nexport type ShopifyRequestContext<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextBase & {\n i18n: NormalizedI18nConfig<I18n>;\n };\n\n/** A request context that includes the customer's IP address. Private Storefront API clients require this context. */\nexport type ShopifyRequestContextWithBuyerIp<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContext<I18n> & { readonly buyerIp: string };\n\n/** The values that the request context reads from the incoming request. */\ntype Context<I18n extends I18nConfig = I18nConfig> = {\n /** The incoming request's `Cookie` header. */\n cookie?: string;\n /** The incoming request's `Sec-GPC` header value, which carries the customer's Global Privacy Control signal. */\n globalPrivacyControl?: string;\n /** The customer's trusted IP address. */\n buyerIp?: string;\n /** The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one. */\n requestGroupId: string;\n /** The incoming request's abort signal. */\n signal?: AbortSignal;\n /** The incoming request's URL. */\n url?: string;\n /** The origin of the incoming request's URL. */\n storefrontOrigin?: string;\n /** The request's language, country, and path prefix. */\n i18n: NormalizedI18nConfig<I18n>;\n /** Whether the incoming request asks for an HTML document. */\n documentRequest?: boolean;\n};\n\n/**\n * Creates the context for one incoming request. Pass the same context to the Storefront API\n * client, the Customer Account API client, and `handleShopifyRoutes`. The context forwards the\n * customer's cookies, locale, and privacy signal to Shopify, and adds Shopify's cookies and cache\n * headers to your response.\n *\n * Call `applyResponseHeaders()` on each response that your app builds. Pass `buyerIp` with the customer's trusted IP address when you use a private Storefront API client. The function throws when `i18n` lacks a country or language, and when `buyerIp` is an empty string.\n *\n * @publicDocs\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextWithBuyerIpInput<I18n>,\n): ShopifyRequestContextWithBuyerIp<I18n>;\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInput<I18n>,\n): ShopifyRequestContext<I18n>;\n/**\n * @param input The incoming request, its locale, and the customer's IP address for private Storefront API clients.\n * @returns The context to pass to Hydrogen's API clients and route handlers for this request.\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInputBase<I18n> & { buyerIp?: string },\n): ShopifyRequestContext<I18n> {\n const { request } = input;\n\n if (!input.i18n?.country || !input.i18n?.language) {\n throw new Error(\"i18n with country and language is required for Shopify request contexts.\");\n }\n\n if (input.buyerIp !== undefined && !input.buyerIp) {\n throw new Error(\"buyerIp must be non-empty when provided\");\n }\n\n const requestMethod = request.method?.toUpperCase();\n const i18n = normalizeI18n(input.i18n);\n const cookieHeader = request.headers.get(\"cookie\") || undefined;\n const inboundCookies = parseCookieHeader(cookieHeader);\n const hasEssentialCookie = inboundCookies.has(SHOPIFY_ESSENTIAL_COOKIE);\n const isConsentManagementRequest = request.headers.get(CONSENT_MANAGEMENT_HEADER) === \"1\";\n const url = request.url ?? request.headers.get(STOREFRONT_URL_HEADER) ?? undefined;\n const storefrontOrigin = getUrlOrigin(url);\n const context = {\n ...(cookieHeader && { cookie: cookieHeader }),\n globalPrivacyControl: request.headers.get(SEC_GPC_HEADER) ?? undefined,\n i18n,\n ...(url && { url }),\n ...(storefrontOrigin && { storefrontOrigin }),\n ...(input.buyerIp && { buyerIp: input.buyerIp }),\n requestGroupId:\n request.headers.get(REQUEST_GROUP_ID_HEADER) ??\n request.headers.get(\"x-request-id\") ??\n request.headers.get(\"request-id\") ??\n crypto.randomUUID(),\n ...(isDocumentRequest(request) && { documentRequest: true }),\n ...(request.signal && { signal: request.signal }),\n } as Context<I18n>;\n\n let capturedCookies: string[] | undefined;\n let personalizedResponseReason: string | undefined;\n let sessionEstablishingReason: string | undefined;\n\n const captureSubrequestHeaders = (headers: Headers): void => {\n // Capture the first fresh response cookies to increase the\n // chance of returning them from the main server response. The main response\n // needs headers set at send time, while the body can stream later, so this\n // may not be used if subrequests finish after the main response is sent.\n const cookies = headers.getSetCookie();\n if (cookies.length > 0) capturedCookies ??= cookies;\n };\n\n return {\n ...context,\n getForwardedRequestHeaders() {\n const headers = new Headers(request.headers);\n applyStorefrontRequestHeaders(context, headers);\n if (context.url) headers.set(STOREFRONT_URL_HEADER, context.url);\n return headers;\n },\n applyStorefrontRequestHeaders(headers) {\n applyStorefrontRequestHeaders(context, headers);\n },\n captureSubrequestHeaders,\n consumeStorefrontResponseHeaders(headers) {\n captureSubrequestHeaders(headers);\n headers.delete(\"set-cookie\");\n },\n markResponseAsPersonalized(reason) {\n personalizedResponseReason ??= reason;\n },\n markResponseAsSessionEstablishing(reason) {\n sessionEstablishingReason ??= reason;\n },\n applyResponseHeaders(headers) {\n headers.set(\"powered-by\", \"Shopify, Hydrogen\");\n\n // Documents may be shared or streamed, so they must not carry buyer-specific state.\n const isDocumentResponse =\n context.documentRequest || (headers.get(\"content-type\")?.startsWith(\"text/html\") ?? false);\n\n // Keep GET and HEAD responses cacheable, and fail closed when the request method is unknown.\n // Cold sessions may only be established by the explicitly marked consent request.\n const mayReturnShopifyState =\n requestMethod !== undefined &&\n requestMethod !== \"GET\" &&\n requestMethod !== \"HEAD\" &&\n !isDocumentResponse &&\n (hasEssentialCookie ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined);\n\n // Replay cookies captured from fresh SFAPI and proxy responses when allowed.\n if (capturedCookies && mayReturnShopifyState) {\n const existingSetCookies = new Set(headers.getSetCookie());\n for (const value of capturedCookies) {\n if (existingSetCookies.has(value)) continue;\n headers.append(\"set-cookie\", value);\n existingSetCookies.add(value);\n }\n }\n\n // Consent/session responses can contain private state in the body without setting cookies.\n if (\n personalizedResponseReason ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined ||\n headers.has(\"set-cookie\")\n ) {\n applyPrivateResponseCacheHeaders(headers);\n }\n },\n } as ShopifyRequestContext<I18n>;\n}\n\nfunction normalizeI18n<I18n extends I18nConfig>(i18n: I18n): NormalizedI18nConfig<I18n> {\n return {\n ...i18n,\n pathPrefix: normalizePathPrefix(i18n.pathPrefix),\n } as NormalizedI18nConfig<I18n>;\n}\n\nfunction applyStorefrontRequestHeaders(context: Context, headers: Headers): void {\n headers.set(SDK_VARIANT_HEADER, \"hydrogen\");\n headers.set(SDK_VARIANT_SOURCE_HEADER, \"kit\");\n headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);\n headers.set(HYDROGEN_VERSION_HEADER, __HYDROGEN_VERSION__);\n headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);\n\n if (context.cookie) headers.set(\"cookie\", context.cookie);\n else headers.delete(\"cookie\");\n if (context.globalPrivacyControl !== undefined) {\n headers.set(SEC_GPC_HEADER, context.globalPrivacyControl);\n } else headers.delete(SEC_GPC_HEADER);\n if (context.storefrontOrigin) {\n headers.set(SHOPIFY_STOREFRONT_ORIGIN_HEADER, context.storefrontOrigin);\n } else headers.delete(SHOPIFY_STOREFRONT_ORIGIN_HEADER);\n}\n\nfunction getUrlOrigin(url: string | undefined): string | undefined {\n if (!url) return undefined;\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nfunction isDocumentRequest(request: StorefrontRequest): boolean {\n if (request.method && request.method !== \"GET\" && request.method !== \"HEAD\") return false;\n\n const destination = request.headers.get(\"sec-fetch-dest\");\n if (destination === \"document\") return true;\n\n return request.headers.get(\"accept\")?.includes(\"text/html\") ?? false;\n}\n\nfunction parseCookieHeader(cookieHeader: string | undefined): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n\n for (const cookie of cookieHeader.split(\";\")) {\n const separator = cookie.indexOf(\"=\");\n if (separator < 0) continue;\n\n const name = cookie.slice(0, separator).trim();\n if (!cookies.has(name)) cookies.set(name, cookie.slice(separator + 1));\n }\n\n return cookies;\n}\n"],"mappings":"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"}
|
|
@@ -2,14 +2,22 @@ import { StorefrontClient } from "../../client/types.mjs";
|
|
|
2
2
|
import { ShopifyRouteTemplates } from "../standard-routes/types.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/core/request-routing/handle-shopify-redirects.d.ts
|
|
5
|
+
/** The unmatched request, the Storefront API client, and your app's route templates. */
|
|
5
6
|
type RedirectOptions = {
|
|
6
|
-
request: Request;
|
|
7
|
-
storefrontClient: StorefrontClient;
|
|
7
|
+
/** The request that your framework's router answered with a `404` response. */request: Request; /** The Storefront API client for the current request. Hydrogen looks up the store's URL redirects with the client, and reads the locale path prefix from the client's request context. */
|
|
8
|
+
storefrontClient: StorefrontClient; /** Your app's route templates. Hydrogen redirects Shopify's default resource paths to these templates. */
|
|
8
9
|
routeTemplates: ShopifyRouteTemplates;
|
|
9
10
|
};
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* Finds the Shopify redirect for a request that your framework's router can't match. Call the
|
|
13
|
+
* function when the router returns a `404` response, and return that `404` response when the function
|
|
14
|
+
* resolves to `null`.
|
|
15
|
+
*
|
|
16
|
+
* The function checks these redirects in order and returns the first match as a `301` response: `/admin` to the store's admin, Shopify's default resource paths to your route templates, same-origin `return_to` and `redirect` query parameters, and the store's URL redirects. The response already carries the request context's headers. When a redirect lookup fails, the function logs the error and resolves to `null`.
|
|
17
|
+
*
|
|
18
|
+
* @param options The unmatched request, the Storefront API client, and your app's route templates.
|
|
19
|
+
* @returns A promise for the matched redirect response, or `null` when no redirect matches.
|
|
20
|
+
* @publicDocs
|
|
13
21
|
*/
|
|
14
22
|
declare function handleShopifyRedirects(options: RedirectOptions): Promise<Response | null>;
|
|
15
23
|
//#endregion
|
|
@@ -8,8 +8,15 @@ import { safeApplyResponseHeaders } from "./safe-apply-response-headers.mjs";
|
|
|
8
8
|
//#region src/core/request-routing/handle-shopify-redirects.ts
|
|
9
9
|
const log = getLogger("redirects");
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Finds the Shopify redirect for a request that your framework's router can't match. Call the
|
|
12
|
+
* function when the router returns a `404` response, and return that `404` response when the function
|
|
13
|
+
* resolves to `null`.
|
|
14
|
+
*
|
|
15
|
+
* The function checks these redirects in order and returns the first match as a `301` response: `/admin` to the store's admin, Shopify's default resource paths to your route templates, same-origin `return_to` and `redirect` query parameters, and the store's URL redirects. The response already carries the request context's headers. When a redirect lookup fails, the function logs the error and resolves to `null`.
|
|
16
|
+
*
|
|
17
|
+
* @param options The unmatched request, the Storefront API client, and your app's route templates.
|
|
18
|
+
* @returns A promise for the matched redirect response, or `null` when no redirect matches.
|
|
19
|
+
* @publicDocs
|
|
13
20
|
*/
|
|
14
21
|
async function handleShopifyRedirects(options) {
|
|
15
22
|
const { request, storefrontClient } = options;
|