@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-shopify-redirects.mjs","names":[],"sources":["../../../../src/core/request-routing/handle-shopify-redirects.ts"],"sourcesContent":["import type { StorefrontClient } from \"../../client\";\nimport { getLogger } from \"../logging\";\nimport type { ShopifyRouteTemplates } from \"../standard-routes/index\";\nimport { handleAdminRedirect } from \"./interceptors/admin-redirect\";\nimport { handleQueryParamRedirect } from \"./interceptors/query-param-redirect\";\nimport { handleStandardRouteRedirects } from \"./interceptors/standard-routes\";\nimport { handleUrlRedirects } from \"./interceptors/url-redirects\";\nimport { safeApplyResponseHeaders } from \"./safe-apply-response-headers\";\n\nconst log = getLogger(\"redirects\");\n\nexport type RedirectOptions = {\n request: Request;\n storefrontClient: StorefrontClient;\n routeTemplates: ShopifyRouteTemplates;\n};\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"handle-shopify-redirects.mjs","names":[],"sources":["../../../../src/core/request-routing/handle-shopify-redirects.ts"],"sourcesContent":["import type { StorefrontClient } from \"../../client\";\nimport { getLogger } from \"../logging\";\nimport type { ShopifyRouteTemplates } from \"../standard-routes/index\";\nimport { handleAdminRedirect } from \"./interceptors/admin-redirect\";\nimport { handleQueryParamRedirect } from \"./interceptors/query-param-redirect\";\nimport { handleStandardRouteRedirects } from \"./interceptors/standard-routes\";\nimport { handleUrlRedirects } from \"./interceptors/url-redirects\";\nimport { safeApplyResponseHeaders } from \"./safe-apply-response-headers\";\n\nconst log = getLogger(\"redirects\");\n\n/** The unmatched request, the Storefront API client, and your app's route templates. */\nexport type RedirectOptions = {\n /** The request that your framework's router answered with a `404` response. */\n request: Request;\n /** 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. */\n storefrontClient: StorefrontClient;\n /** Your app's route templates. Hydrogen redirects Shopify's default resource paths to these templates. */\n routeTemplates: ShopifyRouteTemplates;\n};\n\n/**\n * Finds the Shopify redirect for a request that your framework's router can't match. Call the\n * function when the router returns a `404` response, and return that `404` response when the function\n * resolves to `null`.\n *\n * 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`.\n *\n * @param options The unmatched request, the Storefront API client, and your app's route templates.\n * @returns A promise for the matched redirect response, or `null` when no redirect matches.\n * @publicDocs\n */\nexport async function handleShopifyRedirects(options: RedirectOptions): Promise<Response | null> {\n const { request, storefrontClient } = options;\n let redirect: Response | null = null;\n\n try {\n redirect = handleAdminRedirect(options);\n redirect ??= handleStandardRouteRedirects(options);\n redirect ??= handleQueryParamRedirect(request);\n redirect ??= await handleUrlRedirects(options);\n } catch (error) {\n const url = new URL(request.url);\n log.error(`failed to resolve Shopify redirects for route ${url.pathname}`, { error });\n }\n\n return redirect ? safeApplyResponseHeaders(redirect, storefrontClient.requestContext) : null;\n}\n"],"mappings":";;;;;;;;AASA,MAAM,MAAM,UAAU,YAAY;;;;;;;;;;;;AAuBlC,eAAsB,uBAAuB,SAAoD;CAC/F,MAAM,EAAE,SAAS,qBAAqB;CACtC,IAAI,WAA4B;AAEhC,KAAI;AACF,aAAW,oBAAoB,QAAQ;AACvC,eAAa,6BAA6B,QAAQ;AAClD,eAAa,yBAAyB,QAAQ;AAC9C,eAAa,MAAM,mBAAmB,QAAQ;UACvC,OAAO;EACd,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;AAChC,MAAI,MAAM,iDAAiD,IAAI,YAAY,EAAE,OAAO,CAAC;;AAGvF,QAAO,WAAW,yBAAyB,UAAU,iBAAiB,eAAe,GAAG"}
|
|
@@ -2,9 +2,17 @@ import { HydrogenRouteHandler } from "./route-types.mjs";
|
|
|
2
2
|
import { GraphiQLOptions } from "../types.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/core/request-routing/handle-shopify-routes.development.d.ts
|
|
5
|
+
/** The development-only options for route handling. */
|
|
5
6
|
type HydrogenRoutesDevOptions = {
|
|
6
|
-
graphiql?: GraphiQLOptions;
|
|
7
|
+
/** Settings for the GraphiQL explorer at `/graphiql`. */graphiql?: GraphiQLOptions;
|
|
7
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* The development build of handleShopifyRoutes. Your bundler uses it when it
|
|
11
|
+
* resolves the package's `development` export condition. The function serves the
|
|
12
|
+
* same routes as handleShopifyRoutes, then the GraphiQL explorer at `/graphiql`.
|
|
13
|
+
*
|
|
14
|
+
* @publicDocs
|
|
15
|
+
*/
|
|
8
16
|
declare const handleShopifyRoutesDev: HydrogenRouteHandler<HydrogenRoutesDevOptions>;
|
|
9
17
|
//#endregion
|
|
10
18
|
export { handleShopifyRoutesDev };
|
|
@@ -3,6 +3,13 @@ import { handleShopifyRoutes } from "./handle-shopify-routes.mjs";
|
|
|
3
3
|
import { handleGraphiql } from "./interceptors/graphiql.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/core/request-routing/handle-shopify-routes.development.ts
|
|
6
|
+
/**
|
|
7
|
+
* The development build of handleShopifyRoutes. Your bundler uses it when it
|
|
8
|
+
* resolves the package's `development` export condition. The function serves the
|
|
9
|
+
* same routes as handleShopifyRoutes, then the GraphiQL explorer at `/graphiql`.
|
|
10
|
+
*
|
|
11
|
+
* @publicDocs
|
|
12
|
+
*/
|
|
6
13
|
const handleShopifyRoutesDev = (options) => {
|
|
7
14
|
const productionResult = handleShopifyRoutes(options);
|
|
8
15
|
if (productionResult) return productionResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-shopify-routes.development.mjs","names":[],"sources":["../../../../src/core/request-routing/handle-shopify-routes.development.ts"],"sourcesContent":["import type { GraphiQLOptions } from \"../types\";\nimport { handleShopifyRoutes } from \"./handle-shopify-routes\";\nimport { handleGraphiql } from \"./interceptors/graphiql\";\nimport type { HydrogenRouteHandler } from \"./route-types\";\nimport { safeApplyResponseHeaders } from \"./safe-apply-response-headers\";\n\ntype HydrogenRoutesDevOptions = {\n graphiql?: GraphiQLOptions;\n};\n\nexport const handleShopifyRoutesDev: HydrogenRouteHandler<HydrogenRoutesDevOptions> = (options) => {\n const productionResult = handleShopifyRoutes(options);\n if (productionResult) return productionResult;\n\n const graphiqlResult = handleGraphiql(new URL(options.request.url), options);\n if (!graphiqlResult) return null;\n\n return graphiqlResult.then((response) =>\n safeApplyResponseHeaders(response, options.requestContext),\n );\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"handle-shopify-routes.development.mjs","names":[],"sources":["../../../../src/core/request-routing/handle-shopify-routes.development.ts"],"sourcesContent":["import type { GraphiQLOptions } from \"../types\";\nimport { handleShopifyRoutes } from \"./handle-shopify-routes\";\nimport { handleGraphiql } from \"./interceptors/graphiql\";\nimport type { HydrogenRouteHandler } from \"./route-types\";\nimport { safeApplyResponseHeaders } from \"./safe-apply-response-headers\";\n\n/** The development-only options for route handling. */\ntype HydrogenRoutesDevOptions = {\n /** Settings for the GraphiQL explorer at `/graphiql`. */\n graphiql?: GraphiQLOptions;\n};\n\n/**\n * The development build of handleShopifyRoutes. Your bundler uses it when it\n * resolves the package's `development` export condition. The function serves the\n * same routes as handleShopifyRoutes, then the GraphiQL explorer at `/graphiql`.\n *\n * @publicDocs\n */\nexport const handleShopifyRoutesDev: HydrogenRouteHandler<HydrogenRoutesDevOptions> = (options) => {\n const productionResult = handleShopifyRoutes(options);\n if (productionResult) return productionResult;\n\n const graphiqlResult = handleGraphiql(new URL(options.request.url), options);\n if (!graphiqlResult) return null;\n\n return graphiqlResult.then((response) =>\n safeApplyResponseHeaders(response, options.requestContext),\n );\n};\n"],"mappings":";;;;;;;;;;;;AAmBA,MAAa,0BAA0E,YAAY;CACjG,MAAM,mBAAmB,oBAAoB,QAAQ;AACrD,KAAI,iBAAkB,QAAO;CAE7B,MAAM,iBAAiB,eAAe,IAAI,IAAI,QAAQ,QAAQ,IAAI,EAAE,QAAQ;AAC5E,KAAI,CAAC,eAAgB,QAAO;AAE5B,QAAO,eAAe,MAAM,aAC1B,yBAAyB,UAAU,QAAQ,eAAe,CAC3D"}
|
|
@@ -6,6 +6,7 @@ import { handleBuyPermalinkRedirect, handleCheckoutRedirect } from "./intercepto
|
|
|
6
6
|
import { handleMcpProxy } from "./interceptors/mcp-proxy.mjs";
|
|
7
7
|
import { handleSfapiProxy } from "./interceptors/sfapi-proxy.mjs";
|
|
8
8
|
import { handleUcpMcpProxy } from "./interceptors/ucp-mcp-proxy.mjs";
|
|
9
|
+
import { handleUcpProfileProxy } from "./interceptors/ucp-profile-proxy.mjs";
|
|
9
10
|
import { handleWellKnownProxy } from "./interceptors/well-known.mjs";
|
|
10
11
|
import { handleShopifyRouteHandlers } from "./registered-routes.mjs";
|
|
11
12
|
|
|
@@ -17,17 +18,18 @@ const SHOPIFY_ROUTE_INTERCEPTORS = [
|
|
|
17
18
|
handleShopifyRouteHandlers,
|
|
18
19
|
handleBuyPermalinkRedirect,
|
|
19
20
|
handleCheckoutRedirect,
|
|
21
|
+
handleUcpProfileProxy,
|
|
20
22
|
handleWellKnownProxy,
|
|
21
23
|
handleUcpMcpProxy,
|
|
22
24
|
handleMcpProxy,
|
|
23
25
|
handleAjaxApi
|
|
24
26
|
];
|
|
25
27
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* Serves Shopify's storefront endpoints and your custom route handlers.
|
|
29
|
+
* Returns a promise for the matched response, or `null` when no route matches.
|
|
30
|
+
* The matched response already carries the request context's headers.
|
|
31
|
+
*
|
|
32
|
+
* @publicDocs
|
|
31
33
|
*/
|
|
32
34
|
const handleShopifyRoutes = (options) => {
|
|
33
35
|
if (options.requestContext !== options.storefrontClient.requestContext) throw new Error("handleShopifyRoutes must receive the same requestContext used by storefrontClient.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-shopify-routes.mjs","names":[],"sources":["../../../../src/core/request-routing/handle-shopify-routes.ts"],"sourcesContent":["import { handleProductVariantId } from \"../product/accept-variant-id\";\nimport { handleAjaxApi } from \"./interceptors/ajax-api\";\nimport { handleShopifyApiProxy } from \"./interceptors/api-proxy\";\nimport { handleBuyPermalinkRedirect, handleCheckoutRedirect } from \"./interceptors/checkout\";\nimport { handleMcpProxy } from \"./interceptors/mcp-proxy\";\nimport { handleSfapiProxy } from \"./interceptors/sfapi-proxy\";\nimport { handleUcpMcpProxy } from \"./interceptors/ucp-mcp-proxy\";\nimport { handleWellKnownProxy } from \"./interceptors/well-known\";\nimport { handleShopifyRouteHandlers } from \"./registered-routes\";\nimport type {
|
|
1
|
+
{"version":3,"file":"handle-shopify-routes.mjs","names":[],"sources":["../../../../src/core/request-routing/handle-shopify-routes.ts"],"sourcesContent":["import { handleProductVariantId } from \"../product/accept-variant-id\";\nimport { handleAjaxApi } from \"./interceptors/ajax-api\";\nimport { handleShopifyApiProxy } from \"./interceptors/api-proxy\";\nimport { handleBuyPermalinkRedirect, handleCheckoutRedirect } from \"./interceptors/checkout\";\nimport { handleMcpProxy } from \"./interceptors/mcp-proxy\";\nimport { handleSfapiProxy } from \"./interceptors/sfapi-proxy\";\nimport { handleUcpMcpProxy } from \"./interceptors/ucp-mcp-proxy\";\nimport { handleUcpProfileProxy } from \"./interceptors/ucp-profile-proxy\";\nimport { handleWellKnownProxy } from \"./interceptors/well-known\";\nimport { handleShopifyRouteHandlers } from \"./registered-routes\";\nimport type {\n HydrogenRouteHandler,\n HydrogenRouteInterceptor,\n HydrogenRoutesOptions,\n} from \"./route-types\";\nimport { safeApplyResponseHeaders } from \"./safe-apply-response-headers\";\n\nconst SHOPIFY_ROUTE_INTERCEPTORS = [\n handleShopifyApiProxy,\n handleSfapiProxy,\n handleProductVariantId,\n handleShopifyRouteHandlers,\n handleBuyPermalinkRedirect,\n handleCheckoutRedirect,\n handleUcpProfileProxy,\n handleWellKnownProxy,\n handleUcpMcpProxy,\n handleMcpProxy,\n handleAjaxApi,\n] satisfies readonly HydrogenRouteInterceptor[];\n\n/**\n * Serves Shopify's storefront endpoints and your custom route handlers.\n * Returns a promise for the matched response, or `null` when no route matches.\n * The matched response already carries the request context's headers.\n *\n * @publicDocs\n */\nexport const handleShopifyRoutes: HydrogenRouteHandler = (options) => {\n if (options.requestContext !== options.storefrontClient.requestContext) {\n throw new Error(\n \"handleShopifyRoutes must receive the same requestContext used by storefrontClient.\",\n );\n }\n\n const url = new URL(options.request.url);\n\n for (const interceptor of SHOPIFY_ROUTE_INTERCEPTORS) {\n const responsePromise = interceptor(url, options);\n if (!responsePromise) continue;\n\n return responsePromise.then((response) =>\n safeApplyResponseHeaders(response, options.requestContext),\n );\n }\n\n return null;\n};\n\n/**\n * Serves Shopify's storefront endpoints and your custom route handlers from your app. Call the function at the start of request handling, before your framework's router.\n *\n * When a route matches, the function returns a promise for the response. Return that response as is, because the response already carries the request context's headers. When no route matches, the function returns `null` synchronously, and your framework's router handles the request. The function throws when `requestContext` differs from the Storefront API client's request context. When a route handler throws, the returned promise rejects. Inside a `try` block, use `return await` on the result.\n *\n * The function checks these routes in order and returns the first match: the Shopify API proxy at `/__shopify`, the Storefront API proxy at `/api/{version}/graphql.json`, Liquid-style `?variant=` product URLs, registered handler groups, UCP buy permalinks at `/buy/...`, checkout and cart permalinks, allowlisted `.well-known` resources, the UCP MCP proxy at `/api/ucp/mcp`, the MCP proxy at `/api/mcp`, and AJAX cart URLs.\n *\n * Some routes answer unsupported methods with a `405` response. A registered handler path returns `405` for any method that no handler on that path registers. Checkout and cart permalinks accept only `GET` and `HEAD`. Buy permalinks accept only `GET`, and the UCP MCP proxy accepts only `POST`.\n *\n * When your bundler resolves the package's `development` export condition, the function also serves the GraphiQL explorer at `/graphiql` for GET requests that match no other route, and accepts a `graphiql` option. Set `graphiql.customerAccount` to an object with `apiUrl`, `accessToken`, and `schemaUrl` to add a Customer Account API tab. The explorer skips the tab when it can't fetch the schema.\n *\n * @publicDocs\n */\nexport type HandleShopifyRoutesForDocs =\n /**\n * @param options - The request, its context and session, the Storefront API client, your route templates, and your handler groups.\n * @returns A promise for the matched route's response, or `null` when no route matches.\n */\n (options: HydrogenRoutesOptions) => null | Promise<Response>;\n"],"mappings":";;;;;;;;;;;;;AAiBA,MAAM,6BAA6B;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,MAAa,uBAA6C,YAAY;AACpE,KAAI,QAAQ,mBAAmB,QAAQ,iBAAiB,eACtD,OAAM,IAAI,MACR,qFACD;CAGH,MAAM,MAAM,IAAI,IAAI,QAAQ,QAAQ,IAAI;AAExC,MAAK,MAAM,eAAe,4BAA4B;EACpD,MAAM,kBAAkB,YAAY,KAAK,QAAQ;AACjD,MAAI,CAAC,gBAAiB;AAEtB,SAAO,gBAAgB,MAAM,aAC3B,yBAAyB,UAAU,QAAQ,eAAe,CAC3D;;AAGH,QAAO"}
|
|
@@ -8,16 +8,13 @@ function createProxyInterceptor(descriptor) {
|
|
|
8
8
|
const log = getLogger(descriptor.scope);
|
|
9
9
|
const formatError = descriptor.formatError ?? defaultFormatError;
|
|
10
10
|
return (url, options) => {
|
|
11
|
-
const { request
|
|
11
|
+
const { request } = options;
|
|
12
12
|
if (!descriptor.match.test(url.pathname)) return null;
|
|
13
|
-
if (url.searchParams.has("callback")) return Promise.resolve(
|
|
13
|
+
if (url.searchParams.has("callback")) return Promise.resolve(createProxyRejectionResponse({
|
|
14
14
|
status: 400,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
}));
|
|
20
|
-
if (descriptor.methods && !descriptor.methods.includes(request.method)) return Promise.resolve(new Response(JSON.stringify(formatError("Method Not Allowed")), {
|
|
15
|
+
body: formatError("JSONP requests are not supported")
|
|
16
|
+
}, request.method));
|
|
17
|
+
if (descriptor.methods && !descriptor.methods.includes(request.method)) return Promise.resolve(new Response(request.method === "HEAD" ? null : JSON.stringify(formatError("Method Not Allowed")), {
|
|
21
18
|
status: 405,
|
|
22
19
|
headers: {
|
|
23
20
|
allow: descriptor.methods.join(", "),
|
|
@@ -25,52 +22,80 @@ function createProxyInterceptor(descriptor) {
|
|
|
25
22
|
"cache-control": PROXY_ERROR_CACHE_CONTROL
|
|
26
23
|
}
|
|
27
24
|
}));
|
|
28
|
-
let
|
|
29
|
-
let init;
|
|
25
|
+
let requestInit;
|
|
30
26
|
try {
|
|
31
|
-
|
|
32
|
-
upstreamUrl = new URL(upstreamPathname + url.search, storefrontClient.storeUrl);
|
|
33
|
-
init = {
|
|
34
|
-
method: request.method,
|
|
35
|
-
body: request.body,
|
|
36
|
-
headers: createProxyRequestHeaders(descriptor, {
|
|
37
|
-
...options,
|
|
38
|
-
url
|
|
39
|
-
}),
|
|
40
|
-
signal: AbortSignal.timeout(PROXY_TIMEOUT_MS),
|
|
41
|
-
redirect: descriptor.redirect ?? "manual"
|
|
42
|
-
};
|
|
43
|
-
if (request.body) init.duplex = "half";
|
|
27
|
+
requestInit = createProxyRequestInit(descriptor, options, url);
|
|
44
28
|
} catch (error) {
|
|
45
29
|
log.error("request failed", { error });
|
|
46
|
-
return Promise.resolve(createProxyErrorResponse(error,
|
|
30
|
+
return Promise.resolve(createProxyErrorResponse(error, "setup", descriptor.mapError, formatError, request.method));
|
|
47
31
|
}
|
|
48
|
-
return fetch(upstreamUrl, init).then((upstreamResponse) => {
|
|
49
|
-
const headers = createProxyResponseHeaders(upstreamResponse.headers);
|
|
50
|
-
descriptor.responseHeaders?.prepare?.(headers, {
|
|
51
|
-
...options,
|
|
52
|
-
url,
|
|
53
|
-
response: upstreamResponse
|
|
54
|
-
});
|
|
55
|
-
options.requestContext.consumeStorefrontResponseHeaders(headers);
|
|
56
|
-
return new Response(upstreamResponse.body, {
|
|
57
|
-
status: upstreamResponse.status,
|
|
58
|
-
statusText: upstreamResponse.statusText,
|
|
59
|
-
headers
|
|
60
|
-
});
|
|
61
|
-
}).catch((error) => {
|
|
32
|
+
return fetch(requestInit.upstreamUrl, requestInit.init).then((upstreamResponse) => buildProxyResponse(upstreamResponse, descriptor, options, url, log)).catch((error) => {
|
|
62
33
|
log.error("request failed", { error });
|
|
63
|
-
return createProxyErrorResponse(error,
|
|
34
|
+
return createProxyErrorResponse(error, "fetch", descriptor.mapError, formatError, request.method);
|
|
64
35
|
});
|
|
65
36
|
};
|
|
66
37
|
}
|
|
38
|
+
function createProxyRequestInit(descriptor, options, url) {
|
|
39
|
+
const { request, storefrontClient } = options;
|
|
40
|
+
const upstreamPathname = descriptor.rewritePathname?.(url.pathname) ?? url.pathname;
|
|
41
|
+
const search = descriptor.forwardSearch === false ? "" : url.search;
|
|
42
|
+
const upstreamUrl = new URL(upstreamPathname + search, storefrontClient.storeUrl);
|
|
43
|
+
const init = {
|
|
44
|
+
method: request.method,
|
|
45
|
+
body: request.body,
|
|
46
|
+
headers: createProxyRequestHeaders(descriptor, {
|
|
47
|
+
...options,
|
|
48
|
+
url
|
|
49
|
+
}),
|
|
50
|
+
signal: AbortSignal.timeout(descriptor.timeoutMs ?? PROXY_TIMEOUT_MS),
|
|
51
|
+
redirect: descriptor.redirect ?? "manual"
|
|
52
|
+
};
|
|
53
|
+
if (request.body) init.duplex = "half";
|
|
54
|
+
return {
|
|
55
|
+
upstreamUrl,
|
|
56
|
+
init
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function buildProxyResponse(upstreamResponse, descriptor, options, url, log) {
|
|
60
|
+
const upstream = {
|
|
61
|
+
ok: upstreamResponse.ok,
|
|
62
|
+
status: upstreamResponse.status,
|
|
63
|
+
statusText: upstreamResponse.statusText,
|
|
64
|
+
headers: upstreamResponse.headers
|
|
65
|
+
};
|
|
66
|
+
const rejection = descriptor.responseValidation?.(upstream, options, url);
|
|
67
|
+
if (rejection) {
|
|
68
|
+
upstreamResponse.body?.cancel().catch(() => {});
|
|
69
|
+
if (rejection.logMessage) log.error(rejection.logMessage, rejection.log ?? {});
|
|
70
|
+
return createProxyRejectionResponse(rejection, options.request.method);
|
|
71
|
+
}
|
|
72
|
+
const headers = buildProxyResponseHeaders(upstreamResponse.headers, descriptor.responseHeaders?.allow);
|
|
73
|
+
descriptor.responseHeaders?.prepare?.(headers, {
|
|
74
|
+
...options,
|
|
75
|
+
url,
|
|
76
|
+
response: upstream
|
|
77
|
+
});
|
|
78
|
+
options.requestContext.consumeStorefrontResponseHeaders(headers);
|
|
79
|
+
return new Response(upstreamResponse.body, {
|
|
80
|
+
status: upstreamResponse.status,
|
|
81
|
+
statusText: upstreamResponse.statusText,
|
|
82
|
+
headers
|
|
83
|
+
});
|
|
84
|
+
}
|
|
67
85
|
function defaultFormatError(message) {
|
|
68
86
|
return { error: message };
|
|
69
87
|
}
|
|
70
|
-
function createProxyErrorResponse(error,
|
|
88
|
+
function createProxyErrorResponse(error, phase, mapError, formatError, method) {
|
|
71
89
|
const message = error instanceof Error ? error.message : "Internal proxy error";
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
const mapping = mapError?.(error, phase);
|
|
91
|
+
return createProxyRejectionResponse({
|
|
92
|
+
status: mapping?.status ?? (phase === "setup" ? 500 : 502),
|
|
93
|
+
body: mapping?.body === void 0 ? formatError(message) : mapping.body
|
|
94
|
+
}, method);
|
|
95
|
+
}
|
|
96
|
+
function createProxyRejectionResponse(rejection, method) {
|
|
97
|
+
return new Response(method === "HEAD" ? null : JSON.stringify(rejection.body), {
|
|
98
|
+
status: rejection.status,
|
|
74
99
|
headers: {
|
|
75
100
|
"content-type": "application/json",
|
|
76
101
|
"cache-control": PROXY_ERROR_CACHE_CONTROL
|
|
@@ -81,11 +106,22 @@ function createProxyRequestHeaders(descriptor, options) {
|
|
|
81
106
|
const { request, requestContext } = options;
|
|
82
107
|
const { allow, deny, prepare } = descriptor.requestHeaders;
|
|
83
108
|
const headers = allow ? new Headers(extractHeaders((key) => request.headers.get(key), allow)) : new Headers(request.headers);
|
|
84
|
-
requestContext.applyStorefrontRequestHeaders(headers);
|
|
109
|
+
if (descriptor.requestHeaders.applyStorefrontHeaders !== false) requestContext.applyStorefrontRequestHeaders(headers);
|
|
85
110
|
for (const header of deny ?? []) headers.delete(header);
|
|
86
111
|
prepare?.(headers, options);
|
|
87
112
|
return headers;
|
|
88
113
|
}
|
|
114
|
+
function buildProxyResponseHeaders(upstreamHeaders, allow) {
|
|
115
|
+
if (allow) {
|
|
116
|
+
const headers = new Headers();
|
|
117
|
+
for (const name of allow) {
|
|
118
|
+
const value = upstreamHeaders.get(name);
|
|
119
|
+
if (value !== null) headers.set(name, value);
|
|
120
|
+
}
|
|
121
|
+
return headers;
|
|
122
|
+
}
|
|
123
|
+
return createProxyResponseHeaders(upstreamHeaders);
|
|
124
|
+
}
|
|
89
125
|
function createProxyResponseHeaders(upstreamHeaders) {
|
|
90
126
|
const headers = new Headers(upstreamHeaders);
|
|
91
127
|
headers.delete("content-encoding");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.mjs","names":[],"sources":["../../../../../src/core/request-routing/interceptors/proxy.ts"],"sourcesContent":["import { extractHeaders } from \"../../headers\";\nimport { getLogger } from \"../../logging\";\nimport type { HydrogenRouteInterceptor, HydrogenRoutesOptions } from \"../route-types\";\n\nconst PROXY_TIMEOUT_MS = 30_000;\n\n// Proxy errors are transient and may be buyer-specific: never cache them.\nconst PROXY_ERROR_CACHE_CONTROL = \"no-store\";\n\ntype ProxyHeaderContext = HydrogenRoutesOptions & { url: URL };\n\ntype PrepareHeaders<ExtraContext extends object = object> = (\n headers: Headers,\n context: ProxyHeaderContext & ExtraContext,\n) => void;\n\ntype ProxyRequestHeaderOptions = (\n | { allow: readonly string[]; deny?: never }\n | { allow?: never; deny: readonly string[] }\n) & {\n prepare?: PrepareHeaders;\n};\n\ntype ProxyResponseHeaderOptions = {\n prepare?: PrepareHeaders<{ response: Response }>;\n};\n\ntype ProxyDescriptor = {\n match: RegExp;\n methods?: readonly string[];\n formatError?: (message: string) => unknown;\n redirect?: RequestRedirect;\n scope: string;\n rewritePathname?: (pathname: string) => string;\n requestHeaders: ProxyRequestHeaderOptions;\n responseHeaders?: ProxyResponseHeaderOptions;\n};\n\nexport function createProxyInterceptor(descriptor: ProxyDescriptor): HydrogenRouteInterceptor {\n const log = getLogger(descriptor.scope);\n const formatError = descriptor.formatError ?? defaultFormatError;\n\n return (url, options) => {\n const { request, storefrontClient } = options;\n if (!descriptor.match.test(url.pathname)) return null;\n\n // Disable JSONP requests for security:\n if (url.searchParams.has(\"callback\")) {\n return Promise.resolve(\n new Response(JSON.stringify(formatError(\"JSONP requests are not supported\")), {\n status: 400,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": PROXY_ERROR_CACHE_CONTROL,\n },\n }),\n );\n }\n\n if (descriptor.methods && !descriptor.methods.includes(request.method)) {\n // Method not allowed. Shape the body via formatError, like other errors.\n return Promise.resolve(\n new Response(JSON.stringify(formatError(\"Method Not Allowed\")), {\n status: 405,\n headers: {\n allow: descriptor.methods.join(\", \"),\n \"content-type\": \"application/json\",\n \"cache-control\": PROXY_ERROR_CACHE_CONTROL,\n },\n }),\n );\n }\n\n let upstreamUrl: URL;\n let init: RequestInit & { duplex?: \"half\" };\n try {\n const upstreamPathname = descriptor.rewritePathname?.(url.pathname) ?? url.pathname;\n upstreamUrl = new URL(upstreamPathname + url.search, storefrontClient.storeUrl);\n\n init = {\n method: request.method,\n body: request.body,\n headers: createProxyRequestHeaders(descriptor, { ...options, url }),\n signal: AbortSignal.timeout(PROXY_TIMEOUT_MS),\n redirect: descriptor.redirect ?? \"manual\",\n };\n\n // Node's fetch requires this when forwarding a streaming request body.\n if (request.body) init.duplex = \"half\";\n } catch (error) {\n log.error(\"request failed\", { error });\n return Promise.resolve(createProxyErrorResponse(error, 500, formatError));\n }\n\n return fetch(upstreamUrl, init)\n .then((upstreamResponse) => {\n const headers = createProxyResponseHeaders(upstreamResponse.headers);\n descriptor.responseHeaders?.prepare?.(headers, {\n ...options,\n url,\n response: upstreamResponse,\n });\n options.requestContext.consumeStorefrontResponseHeaders(headers);\n\n return new Response(upstreamResponse.body, {\n status: upstreamResponse.status,\n statusText: upstreamResponse.statusText,\n headers,\n });\n })\n .catch((error) => {\n log.error(\"request failed\", { error });\n return createProxyErrorResponse(error, 502, formatError);\n });\n };\n}\n\nfunction defaultFormatError(message: string): { error: string } {\n return { error: message };\n}\n\nfunction createProxyErrorResponse(\n error: unknown,\n status: number,\n formatError: (message: string) => unknown,\n): Response {\n const message = error instanceof Error ? error.message : \"Internal proxy error\";\n return new Response(JSON.stringify(formatError(message)), {\n status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": PROXY_ERROR_CACHE_CONTROL,\n },\n });\n}\n\nfunction createProxyRequestHeaders(\n descriptor: ProxyDescriptor,\n options: ProxyHeaderContext,\n): Headers {\n const { request, requestContext } = options;\n const { allow, deny, prepare } = descriptor.requestHeaders;\n const headers = allow\n ? new Headers(extractHeaders((key) => request.headers.get(key), allow))\n : new Headers(request.headers);\n\n requestContext.applyStorefrontRequestHeaders(headers);\n\n for (const header of deny ?? []) headers.delete(header);\n prepare?.(headers, options);\n\n return headers;\n}\n\nexport function createProxyResponseHeaders(upstreamHeaders: Headers): Headers {\n const headers = new Headers(upstreamHeaders);\n headers.delete(\"content-encoding\");\n headers.delete(\"content-length\");\n headers.delete(\"server-timing\");\n return headers;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"proxy.mjs","names":[],"sources":["../../../../../src/core/request-routing/interceptors/proxy.ts"],"sourcesContent":["import { extractHeaders } from \"../../headers\";\nimport { getLogger } from \"../../logging\";\nimport type { HydrogenRouteInterceptor, HydrogenRoutesOptions } from \"../route-types\";\n\nconst PROXY_TIMEOUT_MS = 30_000;\n\n// Proxy errors are transient and may be buyer-specific: never cache them.\nconst PROXY_ERROR_CACHE_CONTROL = \"no-store\";\n\ntype ScopedLogger = ReturnType<typeof getLogger>;\n\ntype ProxyHeaderContext = HydrogenRoutesOptions & { url: URL };\n\ntype PrepareHeaders<ExtraContext extends object = object> = (\n headers: Headers,\n context: ProxyHeaderContext & ExtraContext,\n) => void;\n\ntype ProxyRequestHeaderOptions = (\n | { allow: readonly string[]; deny?: never }\n | { allow?: never; deny: readonly string[] }\n) & {\n prepare?: PrepareHeaders;\n applyStorefrontHeaders?: boolean;\n};\n\ntype ProxyUpstreamInfo = {\n ok: boolean;\n status: number;\n statusText: string;\n headers: Headers;\n};\n\ntype ProxyResponseHeaderOptions = {\n allow?: readonly string[];\n prepare?: PrepareHeaders<{ response: ProxyUpstreamInfo }>;\n};\n\ntype ProxyResponseRejection = {\n status: number;\n body: unknown;\n logMessage?: string;\n log?: Record<string, unknown>;\n};\n\ntype ProxyResponseValidation = (\n upstream: ProxyUpstreamInfo,\n options: HydrogenRoutesOptions,\n url: URL,\n) => ProxyResponseRejection | null;\n\ntype ProxyErrorPhase = \"setup\" | \"fetch\";\n\ntype ProxyErrorMapping = { status?: number; body?: unknown };\n\ntype MapProxyError = (error: unknown, phase: ProxyErrorPhase) => ProxyErrorMapping;\n\ntype ProxyDescriptor = {\n match: RegExp;\n methods?: readonly string[];\n formatError?: (message: string) => unknown;\n redirect?: RequestRedirect;\n scope: string;\n timeoutMs?: number;\n forwardSearch?: boolean;\n rewritePathname?: (pathname: string) => string;\n requestHeaders: ProxyRequestHeaderOptions;\n responseHeaders?: ProxyResponseHeaderOptions;\n responseValidation?: ProxyResponseValidation;\n mapError?: MapProxyError;\n};\n\ntype ProxyRequestInit = { upstreamUrl: URL; init: RequestInit & { duplex?: \"half\" } };\n\nexport function createProxyInterceptor(descriptor: ProxyDescriptor): HydrogenRouteInterceptor {\n const log = getLogger(descriptor.scope);\n const formatError = descriptor.formatError ?? defaultFormatError;\n\n return (url, options) => {\n const { request } = options;\n if (!descriptor.match.test(url.pathname)) return null;\n\n // Disable JSONP requests for security:\n if (url.searchParams.has(\"callback\")) {\n return Promise.resolve(\n createProxyRejectionResponse(\n {\n status: 400,\n body: formatError(\"JSONP requests are not supported\"),\n },\n request.method,\n ),\n );\n }\n\n if (descriptor.methods && !descriptor.methods.includes(request.method)) {\n // Method not allowed. Shape the body via formatError, like other errors.\n return Promise.resolve(\n new Response(\n request.method === \"HEAD\" ? null : JSON.stringify(formatError(\"Method Not Allowed\")),\n {\n status: 405,\n headers: {\n allow: descriptor.methods.join(\", \"),\n \"content-type\": \"application/json\",\n \"cache-control\": PROXY_ERROR_CACHE_CONTROL,\n },\n },\n ),\n );\n }\n\n let requestInit: ProxyRequestInit;\n try {\n requestInit = createProxyRequestInit(descriptor, options, url);\n } catch (error) {\n log.error(\"request failed\", { error });\n return Promise.resolve(\n createProxyErrorResponse(error, \"setup\", descriptor.mapError, formatError, request.method),\n );\n }\n\n return fetch(requestInit.upstreamUrl, requestInit.init)\n .then((upstreamResponse) =>\n buildProxyResponse(upstreamResponse, descriptor, options, url, log),\n )\n .catch((error) => {\n log.error(\"request failed\", { error });\n return createProxyErrorResponse(\n error,\n \"fetch\",\n descriptor.mapError,\n formatError,\n request.method,\n );\n });\n };\n}\n\nfunction createProxyRequestInit(\n descriptor: ProxyDescriptor,\n options: HydrogenRoutesOptions,\n url: URL,\n): ProxyRequestInit {\n const { request, storefrontClient } = options;\n const upstreamPathname = descriptor.rewritePathname?.(url.pathname) ?? url.pathname;\n const search = descriptor.forwardSearch === false ? \"\" : url.search;\n const upstreamUrl = new URL(upstreamPathname + search, storefrontClient.storeUrl);\n const init: RequestInit & { duplex?: \"half\" } = {\n method: request.method,\n body: request.body,\n headers: createProxyRequestHeaders(descriptor, { ...options, url }),\n signal: AbortSignal.timeout(descriptor.timeoutMs ?? PROXY_TIMEOUT_MS),\n redirect: descriptor.redirect ?? \"manual\",\n };\n\n // Node's fetch requires this when forwarding a streaming request body.\n if (request.body) init.duplex = \"half\";\n return { upstreamUrl, init };\n}\n\nfunction buildProxyResponse(\n upstreamResponse: Response,\n descriptor: ProxyDescriptor,\n options: HydrogenRoutesOptions,\n url: URL,\n log: ScopedLogger,\n): Response {\n const upstream: ProxyUpstreamInfo = {\n ok: upstreamResponse.ok,\n status: upstreamResponse.status,\n statusText: upstreamResponse.statusText,\n headers: upstreamResponse.headers,\n };\n\n const rejection = descriptor.responseValidation?.(upstream, options, url);\n if (rejection) {\n // Centralized drain: response hooks receive only a body-less view, so the factory\n // owns the sole cancellation and no validator can leak the upstream connection.\n upstreamResponse.body?.cancel().catch(() => {});\n if (rejection.logMessage) log.error(rejection.logMessage, rejection.log ?? {});\n return createProxyRejectionResponse(rejection, options.request.method);\n }\n\n const headers = buildProxyResponseHeaders(\n upstreamResponse.headers,\n descriptor.responseHeaders?.allow,\n );\n descriptor.responseHeaders?.prepare?.(headers, { ...options, url, response: upstream });\n options.requestContext.consumeStorefrontResponseHeaders(headers);\n\n return new Response(upstreamResponse.body, {\n status: upstreamResponse.status,\n statusText: upstreamResponse.statusText,\n headers,\n });\n}\n\nfunction defaultFormatError(message: string): { error: string } {\n return { error: message };\n}\n\nfunction createProxyErrorResponse(\n error: unknown,\n phase: ProxyErrorPhase,\n mapError: MapProxyError | undefined,\n formatError: (message: string) => unknown,\n method: string,\n): Response {\n const message = error instanceof Error ? error.message : \"Internal proxy error\";\n const mapping = mapError?.(error, phase);\n const status = mapping?.status ?? (phase === \"setup\" ? 500 : 502);\n const body = mapping?.body === undefined ? formatError(message) : mapping.body;\n return createProxyRejectionResponse({ status, body }, method);\n}\n\nfunction createProxyRejectionResponse(rejection: ProxyResponseRejection, method: string): Response {\n return new Response(method === \"HEAD\" ? null : JSON.stringify(rejection.body), {\n status: rejection.status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": PROXY_ERROR_CACHE_CONTROL,\n },\n });\n}\n\nfunction createProxyRequestHeaders(\n descriptor: ProxyDescriptor,\n options: ProxyHeaderContext,\n): Headers {\n const { request, requestContext } = options;\n const { allow, deny, prepare } = descriptor.requestHeaders;\n const headers = allow\n ? new Headers(extractHeaders((key) => request.headers.get(key), allow))\n : new Headers(request.headers);\n\n if (descriptor.requestHeaders.applyStorefrontHeaders !== false) {\n requestContext.applyStorefrontRequestHeaders(headers);\n }\n\n for (const header of deny ?? []) headers.delete(header);\n prepare?.(headers, options);\n\n return headers;\n}\n\nfunction buildProxyResponseHeaders(upstreamHeaders: Headers, allow?: readonly string[]): Headers {\n if (allow) {\n const headers = new Headers();\n for (const name of allow) {\n const value = upstreamHeaders.get(name);\n if (value !== null) headers.set(name, value);\n }\n return headers;\n }\n return createProxyResponseHeaders(upstreamHeaders);\n}\n\nexport function createProxyResponseHeaders(upstreamHeaders: Headers): Headers {\n const headers = new Headers(upstreamHeaders);\n headers.delete(\"content-encoding\");\n headers.delete(\"content-length\");\n headers.delete(\"server-timing\");\n return headers;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { UCP_RE } from "../../url.mjs";
|
|
2
|
+
import { createProxyInterceptor } from "./proxy.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/core/request-routing/interceptors/ucp-profile-proxy.ts
|
|
5
|
+
const UCP_CACHE_CONTROL = "public, max-age=60, s-maxage=60, stale-while-revalidate=300, stale-if-error=300";
|
|
6
|
+
const UCP_NO_CACHE_CONTROL = "no-store";
|
|
7
|
+
const UCP_PROFILE_PATH = "/.well-known/ucp";
|
|
8
|
+
const proxyUcpProfileRequest = createProxyInterceptor({
|
|
9
|
+
match: UCP_RE,
|
|
10
|
+
scope: "ucp-profile-proxy",
|
|
11
|
+
timeoutMs: 5e3,
|
|
12
|
+
forwardSearch: false,
|
|
13
|
+
rewritePathname: () => UCP_PROFILE_PATH,
|
|
14
|
+
requestHeaders: {
|
|
15
|
+
allow: ["if-none-match", "if-modified-since"],
|
|
16
|
+
applyStorefrontHeaders: false,
|
|
17
|
+
prepare: (headers) => {
|
|
18
|
+
headers.set("accept", "application/json");
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
responseHeaders: {
|
|
22
|
+
allow: [
|
|
23
|
+
"content-type",
|
|
24
|
+
"etag",
|
|
25
|
+
"last-modified"
|
|
26
|
+
],
|
|
27
|
+
prepare: (headers, { response }) => {
|
|
28
|
+
headers.set("cache-control", response.ok || response.status === 304 ? UCP_CACHE_CONTROL : UCP_NO_CACHE_CONTROL);
|
|
29
|
+
headers.set("access-control-allow-origin", "*");
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
responseValidation: (upstream) => {
|
|
33
|
+
if (upstream.status === 304) return null;
|
|
34
|
+
if (upstream.status === 404) return {
|
|
35
|
+
status: 404,
|
|
36
|
+
body: { error: "Shopify UCP profile not found" }
|
|
37
|
+
};
|
|
38
|
+
const contentType = upstream.headers.get("content-type");
|
|
39
|
+
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
40
|
+
const isRedirect = upstream.status >= 300 && upstream.status < 400;
|
|
41
|
+
if (isRedirect || mediaType !== "application/json") {
|
|
42
|
+
const status = isRedirect || upstream.ok ? 502 : upstream.status;
|
|
43
|
+
return {
|
|
44
|
+
status,
|
|
45
|
+
body: { error: "Shopify UCP profile unavailable" },
|
|
46
|
+
...status === 502 && {
|
|
47
|
+
logMessage: "invalid profile response",
|
|
48
|
+
log: {
|
|
49
|
+
status: upstream.status,
|
|
50
|
+
contentType
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
},
|
|
57
|
+
mapError: (error, phase) => ({
|
|
58
|
+
status: phase === "fetch" && error instanceof DOMException && error.name === "TimeoutError" ? 504 : void 0,
|
|
59
|
+
body: { error: "Unable to fetch the Shopify UCP profile" }
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
const handleUcpProfileProxy = (url, options) => {
|
|
63
|
+
if (options.request.method !== "GET" && options.request.method !== "HEAD") return null;
|
|
64
|
+
return proxyUcpProfileRequest(url, options);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
export { handleUcpProfileProxy };
|
|
69
|
+
//# sourceMappingURL=ucp-profile-proxy.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ucp-profile-proxy.mjs","names":[],"sources":["../../../../../src/core/request-routing/interceptors/ucp-profile-proxy.ts"],"sourcesContent":["import { UCP_RE } from \"../../url\";\nimport type { HydrogenRouteInterceptor } from \"../route-types\";\nimport { createProxyInterceptor } from \"./proxy\";\n\n// UCP requires at least 60s of freshness; limit stale signing keys to a further 300s.\nconst UCP_CACHE_CONTROL =\n \"public, max-age=60, s-maxage=60, stale-while-revalidate=300, stale-if-error=300\";\nconst UCP_NO_CACHE_CONTROL = \"no-store\";\nconst UCP_PROFILE_PATH = \"/.well-known/ucp\";\nconst UCP_FETCH_TIMEOUT_MS = 5_000;\n// Accept is fixed upstream; its Vary header does not describe the downstream representation.\nconst UCP_RESPONSE_HEADERS = [\"content-type\", \"etag\", \"last-modified\"] as const;\n\nconst proxyUcpProfileRequest = createProxyInterceptor({\n match: UCP_RE,\n scope: \"ucp-profile-proxy\",\n timeoutMs: UCP_FETCH_TIMEOUT_MS,\n forwardSearch: false,\n rewritePathname: () => UCP_PROFILE_PATH,\n requestHeaders: {\n allow: [\"if-none-match\", \"if-modified-since\"],\n applyStorefrontHeaders: false,\n prepare: (headers) => {\n headers.set(\"accept\", \"application/json\");\n },\n },\n responseHeaders: {\n allow: UCP_RESPONSE_HEADERS,\n prepare: (headers, { response }) => {\n headers.set(\n \"cache-control\",\n response.ok || response.status === 304 ? UCP_CACHE_CONTROL : UCP_NO_CACHE_CONTROL,\n );\n // The profile is public; match the Online Store so browser-based agents can read it.\n headers.set(\"access-control-allow-origin\", \"*\");\n },\n },\n responseValidation: (upstream) => {\n if (upstream.status === 304) return null;\n if (upstream.status === 404) {\n return {\n status: 404,\n body: { error: \"Shopify UCP profile not found\" },\n };\n }\n\n const contentType = upstream.headers.get(\"content-type\");\n const mediaType = contentType?.split(\";\", 1)[0]?.trim().toLowerCase();\n // UCP profile hosting forbids redirects; 304 revalidation is handled above.\n const isRedirect = upstream.status >= 300 && upstream.status < 400;\n if (isRedirect || mediaType !== \"application/json\") {\n const status = isRedirect || upstream.ok ? 502 : upstream.status;\n return {\n status,\n body: { error: \"Shopify UCP profile unavailable\" },\n ...(status === 502 && {\n logMessage: \"invalid profile response\",\n log: { status: upstream.status, contentType },\n }),\n };\n }\n return null;\n },\n mapError: (error, phase) => ({\n status:\n phase === \"fetch\" && error instanceof DOMException && error.name === \"TimeoutError\"\n ? 504\n : undefined,\n body: { error: \"Unable to fetch the Shopify UCP profile\" },\n }),\n});\n\nexport const handleUcpProfileProxy: HydrogenRouteInterceptor = (url, options) => {\n // Unlike reserved API routes, unsupported discovery methods fall through to the app.\n if (options.request.method !== \"GET\" && options.request.method !== \"HEAD\") return null;\n return proxyUcpProfileRequest(url, options);\n};\n"],"mappings":";;;;AAKA,MAAM,oBACJ;AACF,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAKzB,MAAM,yBAAyB,uBAAuB;CACpD,OAAO;CACP,OAAO;CACP,WAAW;CACX,eAAe;CACf,uBAAuB;CACvB,gBAAgB;EACd,OAAO,CAAC,iBAAiB,oBAAoB;EAC7C,wBAAwB;EACxB,UAAU,YAAY;AACpB,WAAQ,IAAI,UAAU,mBAAmB;;EAE5C;CACD,iBAAiB;EACf,OAAO;GAhBmB;GAAgB;GAAQ;GAgBvB;EAC3B,UAAU,SAAS,EAAE,eAAe;AAClC,WAAQ,IACN,iBACA,SAAS,MAAM,SAAS,WAAW,MAAM,oBAAoB,qBAC9D;AAED,WAAQ,IAAI,+BAA+B,IAAI;;EAElD;CACD,qBAAqB,aAAa;AAChC,MAAI,SAAS,WAAW,IAAK,QAAO;AACpC,MAAI,SAAS,WAAW,IACtB,QAAO;GACL,QAAQ;GACR,MAAM,EAAE,OAAO,iCAAiC;GACjD;EAGH,MAAM,cAAc,SAAS,QAAQ,IAAI,eAAe;EACxD,MAAM,YAAY,aAAa,MAAM,KAAK,EAAE,CAAC,IAAI,MAAM,CAAC,aAAa;EAErE,MAAM,aAAa,SAAS,UAAU,OAAO,SAAS,SAAS;AAC/D,MAAI,cAAc,cAAc,oBAAoB;GAClD,MAAM,SAAS,cAAc,SAAS,KAAK,MAAM,SAAS;AAC1D,UAAO;IACL;IACA,MAAM,EAAE,OAAO,mCAAmC;IAClD,GAAI,WAAW,OAAO;KACpB,YAAY;KACZ,KAAK;MAAE,QAAQ,SAAS;MAAQ;MAAa;KAC9C;IACF;;AAEH,SAAO;;CAET,WAAW,OAAO,WAAW;EAC3B,QACE,UAAU,WAAW,iBAAiB,gBAAgB,MAAM,SAAS,iBACjE,MACA;EACN,MAAM,EAAE,OAAO,2CAA2C;EAC3D;CACF,CAAC;AAEF,MAAa,yBAAmD,KAAK,YAAY;AAE/E,KAAI,QAAQ,QAAQ,WAAW,SAAS,QAAQ,QAAQ,WAAW,OAAQ,QAAO;AAClF,QAAO,uBAAuB,KAAK,QAAQ"}
|
|
@@ -6,7 +6,12 @@ const PLACEHOLDER_BASE = "https://0.0.0.0";
|
|
|
6
6
|
async function handleUrlRedirects({ request, storefrontClient }) {
|
|
7
7
|
const { pathname, searchParams } = new URL(request.url);
|
|
8
8
|
const queryPath = pathname.toLowerCase().replace(/\/+$/, "");
|
|
9
|
-
const
|
|
9
|
+
const result = await storefrontClient.graphql(REDIRECT_QUERY, { variables: { query: `path:${queryPath}` } });
|
|
10
|
+
if (result.errors) {
|
|
11
|
+
const messages = result.errors.map(({ message }) => message).join(", ");
|
|
12
|
+
throw new Error(`Storefront API errors: ${messages}`, { cause: result.errors });
|
|
13
|
+
}
|
|
14
|
+
const target = result.data?.urlRedirects?.edges[0]?.node.target;
|
|
10
15
|
if (!target) return null;
|
|
11
16
|
return createRedirectResponse(createRedirectLocation(target, searchParams));
|
|
12
17
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url-redirects.mjs","names":[],"sources":["../../../../../src/core/request-routing/interceptors/url-redirects.ts"],"sourcesContent":["import { gql } from \"../../../graphql\";\nimport type { RedirectOptions } from \"../handle-shopify-redirects\";\n\ntype UrlRedirectOptions = Pick<RedirectOptions, \"request\" | \"storefrontClient\">;\n\nconst REDIRECT_QUERY = gql(`\n query redirects($query: String) {\n urlRedirects(first: 1, query: $query) {\n edges {\n node {\n target\n }\n }\n }\n }\n`);\n\n// Used as a base for `new URL()` when constructing relative redirect URLs.\n// Stripped from the output — only needed because `new URL` requires a base for relative paths.\nconst PLACEHOLDER_BASE = \"https://0.0.0.0\";\n\nexport async function handleUrlRedirects({\n request,\n storefrontClient,\n}: UrlRedirectOptions): Promise<Response | null> {\n const url = new URL(request.url);\n const { pathname, searchParams } = url;\n\n const queryPath = pathname.toLowerCase().replace(/\\/+$/, \"\");\n const result = await storefrontClient.graphql(REDIRECT_QUERY, {\n variables: { query: `path:${queryPath}` },\n });\n\n const target = result.data?.urlRedirects?.edges[0]?.node.target;\n if (!target) return null;\n\n return createRedirectResponse(createRedirectLocation(target, searchParams));\n}\n\nexport function createRedirectLocation(location: string, searchParams: URLSearchParams): string {\n const url = new URL(location, PLACEHOLDER_BASE);\n\n for (const [key, value] of searchParams) {\n url.searchParams.append(key, value);\n }\n\n return url.toString().replace(PLACEHOLDER_BASE, \"\");\n}\n\nexport function createRedirectResponse(location: string, status: number = 301): Response {\n return new Response(null, {\n status,\n headers: { location },\n });\n}\n"],"mappings":";;;AAKA,MAAA,iBAAA,IAAA,0FAUA;AAIA,MAAA,mBAAA;AAEA,eAAA,mBAAA,EACA,SACA,oBACA;CAEA,MAAA,EAAA,UAAA,iBAAA,IADA,IAAA,QAAA,IACA;CAEA,MAAA,YAAA,SAAA,aAAA,CAAA,QAAA,QAAA,GAAA;
|
|
1
|
+
{"version":3,"file":"url-redirects.mjs","names":[],"sources":["../../../../../src/core/request-routing/interceptors/url-redirects.ts"],"sourcesContent":["import { gql } from \"../../../graphql\";\nimport type { RedirectOptions } from \"../handle-shopify-redirects\";\n\ntype UrlRedirectOptions = Pick<RedirectOptions, \"request\" | \"storefrontClient\">;\n\nconst REDIRECT_QUERY = gql(`\n query redirects($query: String) {\n urlRedirects(first: 1, query: $query) {\n edges {\n node {\n target\n }\n }\n }\n }\n`);\n\n// Used as a base for `new URL()` when constructing relative redirect URLs.\n// Stripped from the output — only needed because `new URL` requires a base for relative paths.\nconst PLACEHOLDER_BASE = \"https://0.0.0.0\";\n\nexport async function handleUrlRedirects({\n request,\n storefrontClient,\n}: UrlRedirectOptions): Promise<Response | null> {\n const url = new URL(request.url);\n const { pathname, searchParams } = url;\n\n const queryPath = pathname.toLowerCase().replace(/\\/+$/, \"\");\n const result = await storefrontClient.graphql(REDIRECT_QUERY, {\n variables: { query: `path:${queryPath}` },\n });\n\n if (result.errors) {\n const messages = result.errors.map(({ message }) => message).join(\", \");\n throw new Error(`Storefront API errors: ${messages}`, { cause: result.errors });\n }\n\n const target = result.data?.urlRedirects?.edges[0]?.node.target;\n if (!target) return null;\n\n return createRedirectResponse(createRedirectLocation(target, searchParams));\n}\n\nexport function createRedirectLocation(location: string, searchParams: URLSearchParams): string {\n const url = new URL(location, PLACEHOLDER_BASE);\n\n for (const [key, value] of searchParams) {\n url.searchParams.append(key, value);\n }\n\n return url.toString().replace(PLACEHOLDER_BASE, \"\");\n}\n\nexport function createRedirectResponse(location: string, status: number = 301): Response {\n return new Response(null, {\n status,\n headers: { location },\n });\n}\n"],"mappings":";;;AAKA,MAAA,iBAAA,IAAA,0FAUA;AAIA,MAAA,mBAAA;AAEA,eAAA,mBAAA,EACA,SACA,oBACA;CAEA,MAAA,EAAA,UAAA,iBAAA,IADA,IAAA,QAAA,IACA;CAEA,MAAA,YAAA,SAAA,aAAA,CAAA,QAAA,QAAA,GAAA;CACA,MAAA,SAAA,MAAA,iBAAA,QAAA,gBAAA,EACA,WAAA,EAAA,OAAA,QAAA,aAAA,EACA,CAAA;AAEA,KAAA,OAAA,QAAA;EACA,MAAA,WAAA,OAAA,OAAA,KAAA,EAAA,cAAA,QAAA,CAAA,KAAA,KAAA;AACA,QAAA,IAAA,MAAA,0BAAA,YAAA,EAAA,OAAA,OAAA,QAAA,CAAA;;CAGA,MAAA,SAAA,OAAA,MAAA,cAAA,MAAA,IAAA,KAAA;AACA,KAAA,CAAA,OAAA,QAAA;AAEA,QAAA,uBAAA,uBAAA,QAAA,aAAA,CAAA;;AAGA,SAAA,uBAAA,UAAA,cAAA;CACA,MAAA,MAAA,IAAA,IAAA,UAAA,iBAAA;AAEA,MAAA,MAAA,CAAA,KAAA,UAAA,aACA,KAAA,aAAA,OAAA,KAAA,MAAA;AAGA,QAAA,IAAA,UAAA,CAAA,QAAA,kBAAA,GAAA;;AAGA,SAAA,uBAAA,UAAA,SAAA,KAAA;AACA,QAAA,IAAA,SAAA,MAAA;EACA;EACA,SAAA,EAAA,UAAA;EACA,CAAA"}
|
|
@@ -7,7 +7,7 @@ import { createProxyInterceptor } from "./proxy.mjs";
|
|
|
7
7
|
* Proxies allowlisted Shopify `.well-known` resources through the headless
|
|
8
8
|
* storefront origin to the Online Store, so first-party services (Apple Pay
|
|
9
9
|
* domain verification, Frontend Event Collector ingress) behave the same way
|
|
10
|
-
* they do on Online Store storefronts. See
|
|
10
|
+
* they do on Online Store storefronts. See `WELL_KNOWN_RE` for the
|
|
11
11
|
* allowlist.
|
|
12
12
|
*/
|
|
13
13
|
const handleWellKnownProxy = createProxyInterceptor({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"well-known.mjs","names":[],"sources":["../../../../../src/core/request-routing/interceptors/well-known.ts"],"sourcesContent":["import { PROXY_REQUEST_HEADER_DENYLIST } from \"../../headers\";\nimport { WELL_KNOWN_RE } from \"../../url\";\nimport { createProxyInterceptor } from \"./proxy\";\n\n/**\n * Proxies allowlisted Shopify `.well-known` resources through the headless\n * storefront origin to the Online Store, so first-party services (Apple Pay\n * domain verification, Frontend Event Collector ingress) behave the same way\n * they do on Online Store storefronts. See
|
|
1
|
+
{"version":3,"file":"well-known.mjs","names":[],"sources":["../../../../../src/core/request-routing/interceptors/well-known.ts"],"sourcesContent":["import { PROXY_REQUEST_HEADER_DENYLIST } from \"../../headers\";\nimport { WELL_KNOWN_RE } from \"../../url\";\nimport { createProxyInterceptor } from \"./proxy\";\n\n/**\n * Proxies allowlisted Shopify `.well-known` resources through the headless\n * storefront origin to the Online Store, so first-party services (Apple Pay\n * domain verification, Frontend Event Collector ingress) behave the same way\n * they do on Online Store storefronts. See `WELL_KNOWN_RE` for the\n * allowlist.\n */\nexport const handleWellKnownProxy = createProxyInterceptor({\n match: WELL_KNOWN_RE,\n requestHeaders: { deny: PROXY_REQUEST_HEADER_DENYLIST },\n redirect: \"follow\",\n scope: \"well-known-proxy\",\n});\n"],"mappings":";;;;;;;;;;;;AAWA,MAAa,uBAAuB,uBAAuB;CACzD,OAAO;CACP,gBAAgB,EAAE,MAAM,+BAA+B;CACvD,UAAU;CACV,OAAO;CACR,CAAC"}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { CallableRouteHandler, ShopifyRedirectStatus, ShopifyRouteError, ShopifyRouteErrorResult, ShopifyRouteHandler, ShopifyRouteHandlerContext, ShopifyRouteHandlerGroup, ShopifyRouteHandlerResult, ShopifyRouteJsonResult, ShopifyRouteRedirectResult, ShopifyRouteSessionManager } from "./route-types.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/core/request-routing/registered-routes.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Creates a custom endpoint in your app for one pathname and HTTP method. Collect handlers in an
|
|
6
|
+
* object, and pass an array of handler objects to `handleShopifyRoutes` in its `handlers` option.
|
|
7
|
+
*
|
|
8
|
+
* Use paths other than `/api/cart`, which the cart server handlers use. Return a JSON, redirect, or error result from the handler. Hydrogen sends a JSON result with status `200`, an error result with status `400` by default, and a redirect result with status `303` by default. When a redirect result uses a status other than `301`, `302`, `303`, 307, or 308, the promise from `handleShopifyRoutes` rejects.
|
|
9
|
+
*
|
|
10
|
+
* @param pathname The exact URL pathname that the handler matches. Hydrogen doesn't match path parameters or prefixes.
|
|
11
|
+
* @param method The HTTP method that the handler matches, in uppercase, such as `POST`.
|
|
12
|
+
* @param handler The function that receives the request, its context, the session, and the Storefront API client, and returns a JSON, redirect, or error result.
|
|
13
|
+
* @returns The handler to add to a handler object.
|
|
14
|
+
* @publicDocs
|
|
15
|
+
*/
|
|
4
16
|
declare function createShopifyRouteHandler<const TPathname extends string, const TMethod extends string>(pathname: TPathname, method: TMethod, handler: (context: ShopifyRouteHandlerContext) => Promise<ShopifyRouteHandlerResult>): ShopifyRouteHandler<TPathname, TMethod>;
|
|
5
17
|
//#endregion
|
|
6
18
|
export { createShopifyRouteHandler };
|
|
@@ -10,6 +10,18 @@ const VALID_REDIRECT_STATUSES = [
|
|
|
10
10
|
307,
|
|
11
11
|
308
|
|
12
12
|
];
|
|
13
|
+
/**
|
|
14
|
+
* Creates a custom endpoint in your app for one pathname and HTTP method. Collect handlers in an
|
|
15
|
+
* object, and pass an array of handler objects to `handleShopifyRoutes` in its `handlers` option.
|
|
16
|
+
*
|
|
17
|
+
* Use paths other than `/api/cart`, which the cart server handlers use. Return a JSON, redirect, or error result from the handler. Hydrogen sends a JSON result with status `200`, an error result with status `400` by default, and a redirect result with status `303` by default. When a redirect result uses a status other than `301`, `302`, `303`, 307, or 308, the promise from `handleShopifyRoutes` rejects.
|
|
18
|
+
*
|
|
19
|
+
* @param pathname The exact URL pathname that the handler matches. Hydrogen doesn't match path parameters or prefixes.
|
|
20
|
+
* @param method The HTTP method that the handler matches, in uppercase, such as `POST`.
|
|
21
|
+
* @param handler The function that receives the request, its context, the session, and the Storefront API client, and returns a JSON, redirect, or error result.
|
|
22
|
+
* @returns The handler to add to a handler object.
|
|
23
|
+
* @publicDocs
|
|
24
|
+
*/
|
|
13
25
|
function createShopifyRouteHandler(pathname, method, handler) {
|
|
14
26
|
return createCallableRouteHandler(pathname, method, handler);
|
|
15
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registered-routes.mjs","names":[],"sources":["../../../../src/core/request-routing/registered-routes.ts"],"sourcesContent":["import type {\n CallableRouteHandler,\n HydrogenRouteInterceptor,\n ShopifyRouteHandler,\n ShopifyRouteHandlerContext,\n ShopifyRouteHandlerResult,\n ShopifyRedirectStatus,\n} from \"./route-types\";\n\nexport type {\n CallableRouteHandler,\n ShopifyRouteError,\n ShopifyRouteErrorResult,\n ShopifyRouteHandler,\n ShopifyRouteHandlerContext,\n ShopifyRouteHandlerGroup,\n ShopifyRouteHandlerResult,\n ShopifyRouteJsonResult,\n ShopifyRouteRedirectResult,\n ShopifyRedirectStatus,\n ShopifyRouteSessionManager,\n} from \"./route-types\";\n\nconst HTTP_OK_STATUS = 200;\nconst HTTP_SEE_OTHER_STATUS = 303;\nconst HTTP_METHOD_NOT_ALLOWED_STATUS = 405;\nconst HTTP_BAD_REQUEST_STATUS = 400;\nconst VALID_REDIRECT_STATUSES = [\n 301, 302, 303, 307, 308,\n] as const satisfies readonly ShopifyRedirectStatus[];\n\nexport function createShopifyRouteHandler<\n const TPathname extends string,\n const TMethod extends string,\n>(\n pathname: TPathname,\n method: TMethod,\n handler: (context: ShopifyRouteHandlerContext) => Promise<ShopifyRouteHandlerResult>,\n): ShopifyRouteHandler<TPathname, TMethod> {\n return createCallableRouteHandler(pathname, method, handler);\n}\n\nexport function createCallableRouteHandler<\n const TPathname extends string,\n const TMethod extends string,\n TContext,\n TResult,\n>(\n pathname: TPathname,\n method: TMethod,\n handler: (context: TContext) => Promise<TResult>,\n): CallableRouteHandler<TContext, TResult, TPathname, TMethod> {\n return Object.assign(handler, { pathname, method });\n}\n\nexport const handleShopifyRouteHandlers: HydrogenRouteInterceptor = (\n url,\n { request, sessionManager, storefrontClient, requestContext, handlers = [] },\n) => {\n const context = { request, sessionManager, storefrontClient, requestContext };\n const routeHandlers = handlers.flatMap((group) => Object.values(group));\n if (routeHandlers.length === 0) return null;\n\n const pathMatches = routeHandlers.filter((entry) => entry.pathname === url.pathname);\n if (pathMatches.length === 0) return null;\n\n const match = pathMatches.find((candidate) => candidate.method === request.method);\n if (!match)\n return Promise.resolve(\n new Response(\"Method Not Allowed\", { status: HTTP_METHOD_NOT_ALLOWED_STATUS }),\n );\n\n return match(context).then((result) => createShopifyRouteResponse(result, request));\n};\n\nfunction createShopifyRouteResponse(result: ShopifyRouteHandlerResult, request: Request): Response {\n if (result.type === \"redirect\") {\n const headers = new Headers(result.headers);\n headers.set(\"location\", resolveRedirectLocation(result.location, request));\n return new Response(null, {\n status: getRedirectStatus(result.status),\n headers,\n });\n }\n\n if (result.type === \"error\") {\n const headers = new Headers(result.headers);\n headers.set(\"content-type\", \"application/json\");\n return new Response(JSON.stringify({ error: result.error }), {\n status: result.status ?? HTTP_BAD_REQUEST_STATUS,\n headers,\n });\n }\n\n const headers = new Headers(result.headers);\n headers.set(\"content-type\", \"application/json\");\n return new Response(JSON.stringify(result.data), {\n status: HTTP_OK_STATUS,\n headers,\n });\n}\n\nfunction getRedirectStatus(status: ShopifyRedirectStatus | undefined): ShopifyRedirectStatus {\n const redirectStatus = status ?? HTTP_SEE_OTHER_STATUS;\n if (VALID_REDIRECT_STATUSES.some((validStatus) => validStatus === redirectStatus)) {\n return redirectStatus;\n }\n\n throw new Error(\n `Invalid Shopify route redirect status ${redirectStatus}. Expected one of: ${VALID_REDIRECT_STATUSES.join(\", \")}.`,\n );\n}\n\nfunction resolveRedirectLocation(location: string, request: Request): string {\n // Absolute Location headers are the best common denominator: browsers accept\n // relative redirects, but framework proxy runtimes like Next.js can require\n // absolute URLs when returning a Response from middleware/proxy code.\n return new URL(location, new URL(request.url).origin).toString();\n}\n"],"mappings":";AAuBA,MAAM,iBAAiB;AACvB,MAAM,wBAAwB;AAC9B,MAAM,iCAAiC;AACvC,MAAM,0BAA0B;AAChC,MAAM,0BAA0B;CAC9B;CAAK;CAAK;CAAK;CAAK;CACrB
|
|
1
|
+
{"version":3,"file":"registered-routes.mjs","names":[],"sources":["../../../../src/core/request-routing/registered-routes.ts"],"sourcesContent":["import type {\n CallableRouteHandler,\n HydrogenRouteInterceptor,\n ShopifyRouteHandler,\n ShopifyRouteHandlerContext,\n ShopifyRouteHandlerResult,\n ShopifyRedirectStatus,\n} from \"./route-types\";\n\nexport type {\n CallableRouteHandler,\n ShopifyRouteError,\n ShopifyRouteErrorResult,\n ShopifyRouteHandler,\n ShopifyRouteHandlerContext,\n ShopifyRouteHandlerGroup,\n ShopifyRouteHandlerResult,\n ShopifyRouteJsonResult,\n ShopifyRouteRedirectResult,\n ShopifyRedirectStatus,\n ShopifyRouteSessionManager,\n} from \"./route-types\";\n\nconst HTTP_OK_STATUS = 200;\nconst HTTP_SEE_OTHER_STATUS = 303;\nconst HTTP_METHOD_NOT_ALLOWED_STATUS = 405;\nconst HTTP_BAD_REQUEST_STATUS = 400;\nconst VALID_REDIRECT_STATUSES = [\n 301, 302, 303, 307, 308,\n] as const satisfies readonly ShopifyRedirectStatus[];\n\n/**\n * Creates a custom endpoint in your app for one pathname and HTTP method. Collect handlers in an\n * object, and pass an array of handler objects to `handleShopifyRoutes` in its `handlers` option.\n *\n * Use paths other than `/api/cart`, which the cart server handlers use. Return a JSON, redirect, or error result from the handler. Hydrogen sends a JSON result with status `200`, an error result with status `400` by default, and a redirect result with status `303` by default. When a redirect result uses a status other than `301`, `302`, `303`, 307, or 308, the promise from `handleShopifyRoutes` rejects.\n *\n * @param pathname The exact URL pathname that the handler matches. Hydrogen doesn't match path parameters or prefixes.\n * @param method The HTTP method that the handler matches, in uppercase, such as `POST`.\n * @param handler The function that receives the request, its context, the session, and the Storefront API client, and returns a JSON, redirect, or error result.\n * @returns The handler to add to a handler object.\n * @publicDocs\n */\nexport function createShopifyRouteHandler<\n const TPathname extends string,\n const TMethod extends string,\n>(\n pathname: TPathname,\n method: TMethod,\n handler: (context: ShopifyRouteHandlerContext) => Promise<ShopifyRouteHandlerResult>,\n): ShopifyRouteHandler<TPathname, TMethod> {\n return createCallableRouteHandler(pathname, method, handler);\n}\n\nexport function createCallableRouteHandler<\n const TPathname extends string,\n const TMethod extends string,\n TContext,\n TResult,\n>(\n pathname: TPathname,\n method: TMethod,\n handler: (context: TContext) => Promise<TResult>,\n): CallableRouteHandler<TContext, TResult, TPathname, TMethod> {\n return Object.assign(handler, { pathname, method });\n}\n\nexport const handleShopifyRouteHandlers: HydrogenRouteInterceptor = (\n url,\n { request, sessionManager, storefrontClient, requestContext, handlers = [] },\n) => {\n const context = { request, sessionManager, storefrontClient, requestContext };\n const routeHandlers = handlers.flatMap((group) => Object.values(group));\n if (routeHandlers.length === 0) return null;\n\n const pathMatches = routeHandlers.filter((entry) => entry.pathname === url.pathname);\n if (pathMatches.length === 0) return null;\n\n const match = pathMatches.find((candidate) => candidate.method === request.method);\n if (!match)\n return Promise.resolve(\n new Response(\"Method Not Allowed\", { status: HTTP_METHOD_NOT_ALLOWED_STATUS }),\n );\n\n return match(context).then((result) => createShopifyRouteResponse(result, request));\n};\n\nfunction createShopifyRouteResponse(result: ShopifyRouteHandlerResult, request: Request): Response {\n if (result.type === \"redirect\") {\n const headers = new Headers(result.headers);\n headers.set(\"location\", resolveRedirectLocation(result.location, request));\n return new Response(null, {\n status: getRedirectStatus(result.status),\n headers,\n });\n }\n\n if (result.type === \"error\") {\n const headers = new Headers(result.headers);\n headers.set(\"content-type\", \"application/json\");\n return new Response(JSON.stringify({ error: result.error }), {\n status: result.status ?? HTTP_BAD_REQUEST_STATUS,\n headers,\n });\n }\n\n const headers = new Headers(result.headers);\n headers.set(\"content-type\", \"application/json\");\n return new Response(JSON.stringify(result.data), {\n status: HTTP_OK_STATUS,\n headers,\n });\n}\n\nfunction getRedirectStatus(status: ShopifyRedirectStatus | undefined): ShopifyRedirectStatus {\n const redirectStatus = status ?? HTTP_SEE_OTHER_STATUS;\n if (VALID_REDIRECT_STATUSES.some((validStatus) => validStatus === redirectStatus)) {\n return redirectStatus;\n }\n\n throw new Error(\n `Invalid Shopify route redirect status ${redirectStatus}. Expected one of: ${VALID_REDIRECT_STATUSES.join(\", \")}.`,\n );\n}\n\nfunction resolveRedirectLocation(location: string, request: Request): string {\n // Absolute Location headers are the best common denominator: browsers accept\n // relative redirects, but framework proxy runtimes like Next.js can require\n // absolute URLs when returning a Response from middleware/proxy code.\n return new URL(location, new URL(request.url).origin).toString();\n}\n"],"mappings":";AAuBA,MAAM,iBAAiB;AACvB,MAAM,wBAAwB;AAC9B,MAAM,iCAAiC;AACvC,MAAM,0BAA0B;AAChC,MAAM,0BAA0B;CAC9B;CAAK;CAAK;CAAK;CAAK;CACrB;;;;;;;;;;;;;AAcD,SAAgB,0BAId,UACA,QACA,SACyC;AACzC,QAAO,2BAA2B,UAAU,QAAQ,QAAQ;;AAG9D,SAAgB,2BAMd,UACA,QACA,SAC6D;AAC7D,QAAO,OAAO,OAAO,SAAS;EAAE;EAAU;EAAQ,CAAC;;AAGrD,MAAa,8BACX,KACA,EAAE,SAAS,gBAAgB,kBAAkB,gBAAgB,WAAW,EAAE,OACvE;CACH,MAAM,UAAU;EAAE;EAAS;EAAgB;EAAkB;EAAgB;CAC7E,MAAM,gBAAgB,SAAS,SAAS,UAAU,OAAO,OAAO,MAAM,CAAC;AACvE,KAAI,cAAc,WAAW,EAAG,QAAO;CAEvC,MAAM,cAAc,cAAc,QAAQ,UAAU,MAAM,aAAa,IAAI,SAAS;AACpF,KAAI,YAAY,WAAW,EAAG,QAAO;CAErC,MAAM,QAAQ,YAAY,MAAM,cAAc,UAAU,WAAW,QAAQ,OAAO;AAClF,KAAI,CAAC,MACH,QAAO,QAAQ,QACb,IAAI,SAAS,sBAAsB,EAAE,QAAQ,gCAAgC,CAAC,CAC/E;AAEH,QAAO,MAAM,QAAQ,CAAC,MAAM,WAAW,2BAA2B,QAAQ,QAAQ,CAAC;;AAGrF,SAAS,2BAA2B,QAAmC,SAA4B;AACjG,KAAI,OAAO,SAAS,YAAY;EAC9B,MAAM,UAAU,IAAI,QAAQ,OAAO,QAAQ;AAC3C,UAAQ,IAAI,YAAY,wBAAwB,OAAO,UAAU,QAAQ,CAAC;AAC1E,SAAO,IAAI,SAAS,MAAM;GACxB,QAAQ,kBAAkB,OAAO,OAAO;GACxC;GACD,CAAC;;AAGJ,KAAI,OAAO,SAAS,SAAS;EAC3B,MAAM,UAAU,IAAI,QAAQ,OAAO,QAAQ;AAC3C,UAAQ,IAAI,gBAAgB,mBAAmB;AAC/C,SAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,OAAO,OAAO,CAAC,EAAE;GAC3D,QAAQ,OAAO,UAAU;GACzB;GACD,CAAC;;CAGJ,MAAM,UAAU,IAAI,QAAQ,OAAO,QAAQ;AAC3C,SAAQ,IAAI,gBAAgB,mBAAmB;AAC/C,QAAO,IAAI,SAAS,KAAK,UAAU,OAAO,KAAK,EAAE;EAC/C,QAAQ;EACR;EACD,CAAC;;AAGJ,SAAS,kBAAkB,QAAkE;CAC3F,MAAM,iBAAiB,UAAU;AACjC,KAAI,wBAAwB,MAAM,gBAAgB,gBAAgB,eAAe,CAC/E,QAAO;AAGT,OAAM,IAAI,MACR,yCAAyC,eAAe,qBAAqB,wBAAwB,KAAK,KAAK,CAAC,GACjH;;AAGH,SAAS,wBAAwB,UAAkB,SAA0B;AAI3E,QAAO,IAAI,IAAI,UAAU,IAAI,IAAI,QAAQ,IAAI,CAAC,OAAO,CAAC,UAAU"}
|