@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":"strategies.mjs","names":[],"sources":["../../../../src/core/cache/strategies.ts"],"sourcesContent":["export const NO_STORE = \"no-store\";\n\nconst PUBLIC = \"public\";\nconst PRIVATE = \"private\";\n\nexport type CacheMode = typeof PUBLIC | typeof PRIVATE | typeof NO_STORE;\nexport type ExpirableCacheMode = typeof PUBLIC | typeof PRIVATE;\n\nexport type CacheDuration =\n | number\n | {\n seconds?: number;\n minutes?: number;\n hours?: number;\n days?: number;\n };\n\nexport interface CachingStrategy {\n mode?: CacheMode;\n maxAge?: number;\n staleWhileRevalidate?: number;\n staleIfError?: number;\n}\n\nexport type CacheOptions = {\n mode?: ExpirableCacheMode;\n maxAge?: CacheDuration;\n staleWhileRevalidate?: CacheDuration;\n staleIfError?: CacheDuration;\n};\n\ntype NoStoreStrategy = CachingStrategy & {\n mode: typeof NO_STORE;\n};\n\nexport const Cache = Object.assign(createCache, {\n none: cacheNone,\n short: cacheShort,\n long: cacheLong,\n});\n\nexport function getCacheRetentionTtl(strategy: CachingStrategy): number {\n if (strategy.mode === NO_STORE) return 0;\n\n return Math.max(\n 0,\n (strategy.maxAge ?? 0) + (strategy.staleWhileRevalidate ?? 0) + (strategy.staleIfError ?? 0),\n );\n}\n\nexport function getPlatformCacheControlHeader(strategy: CachingStrategy): string {\n const ttl = getCacheRetentionTtl(strategy);\n if (ttl <= 0) return NO_STORE;\n\n return `${strategy.mode ?? PUBLIC}, max-age=${Math.ceil(ttl)}`;\n}\n\nfunction createCache(options: CacheOptions): CachingStrategy {\n return normalizeCacheOptions(options);\n}\n\nfunction cacheNone(): NoStoreStrategy {\n return { mode: NO_STORE };\n}\n\nfunction cacheShort(overrideOptions?: CacheOptions): CachingStrategy {\n return normalizeCacheOptions(\n {\n mode: PUBLIC,\n maxAge: 1,\n staleWhileRevalidate: 9,\n },\n overrideOptions,\n );\n}\n\nfunction cacheLong(overrideOptions?: CacheOptions): CachingStrategy {\n return normalizeCacheOptions(\n {\n mode: PUBLIC,\n maxAge: 3600,\n staleWhileRevalidate: 82800,\n },\n overrideOptions,\n );\n}\n\nfunction normalizeCacheOptions(\n defaults: CacheOptions,\n overrideOptions?: CacheOptions,\n): CachingStrategy;\nfunction normalizeCacheOptions(options: CacheOptions): CachingStrategy;\nfunction normalizeCacheOptions(\n defaultsOrOptions: CacheOptions,\n overrideOptions?: CacheOptions,\n): CachingStrategy {\n const options = {\n ...defaultsOrOptions,\n ...overrideOptions,\n };\n\n return {\n ...normalizeMode(options),\n ...normalizeDurationOption(\"maxAge\", options.maxAge),\n ...normalizeDurationOption(\"staleWhileRevalidate\", options.staleWhileRevalidate),\n ...normalizeDurationOption(\"staleIfError\", options.staleIfError),\n };\n}\n\nfunction normalizeMode(options: CacheOptions): Pick<CachingStrategy, \"mode\"> {\n if (options.mode && options.mode !== PUBLIC && options.mode !== PRIVATE) {\n throw new Error(\"'mode' must be either 'public' or 'private'\");\n }\n\n return { mode: options.mode ?? PUBLIC };\n}\n\nfunction normalizeDurationOption(\n name: \"maxAge\" | \"staleWhileRevalidate\" | \"staleIfError\",\n duration: CacheDuration | undefined,\n): Pick<CachingStrategy, typeof name> {\n if (duration == null) return {};\n\n return { [name]: normalizeDuration(duration) };\n}\n\nfunction normalizeDuration(duration: CacheDuration): number {\n if (typeof duration === \"number\") return validateDuration(duration);\n\n return validateDuration(\n (duration.seconds ?? 0) +\n (duration.minutes ?? 0) * 60 +\n (duration.hours ?? 0) * 3600 +\n (duration.days ?? 0) * 86400,\n );\n}\n\nfunction validateDuration(duration: number): number {\n if (!Number.isFinite(duration) || duration < 0) {\n throw new Error(\"Cache durations must be finite, non-negative numbers.\");\n }\n\n return Math.ceil(duration);\n}\n"],"mappings":";AAAA,MAAa,WAAW;AAExB,MAAM,SAAS;AACf,MAAM,UAAU
|
|
1
|
+
{"version":3,"file":"strategies.mjs","names":[],"sources":["../../../../src/core/cache/strategies.ts"],"sourcesContent":["export const NO_STORE = \"no-store\";\n\nconst PUBLIC = \"public\";\nconst PRIVATE = \"private\";\n\n/** The cache mode of a strategy, `public`, `private`, or `no-store`. A `no-store` strategy skips the cache. */\nexport type CacheMode = typeof PUBLIC | typeof PRIVATE | typeof NO_STORE;\n/** The modes that a custom strategy accepts, `public` or `private`. `Cache` throws an error for any other mode. */\nexport type ExpirableCacheMode = typeof PUBLIC | typeof PRIVATE;\n\n/**\n * A number of seconds, or an object such as `{ minutes: 10 }`. `Cache` throws an error for a duration that isn't a finite, non-negative number, and rounds fractional totals up to whole seconds.\n */\nexport type CacheDuration =\n | number\n | {\n seconds?: number;\n minutes?: number;\n hours?: number;\n days?: number;\n };\n\n/** A caching strategy, with every duration in whole seconds. Create a strategy with `Cache`. */\nexport interface CachingStrategy {\n /** The cache mode. A `no-store` strategy skips the cache. */\n mode?: CacheMode;\n /** How many seconds an entry stays fresh. */\n maxAge?: number;\n /** How many seconds after the fresh window the cache serves a stale entry while it refreshes the entry in the background. */\n staleWhileRevalidate?: number;\n /** How many seconds after the stale window the cache serves a stale entry when the refresh fails. */\n staleIfError?: number;\n}\n\n/**\n * Options for a custom caching strategy. A duration that you leave out counts as zero seconds.\n *\n * @publicDocs\n */\nexport type CacheOptions = {\n /** The cache mode, `public` or `private`. Defaults to `public`. Web Cache stores receive the mode in the `cache-control` header. The Storefront API client throws an error for `private`. */\n mode?: ExpirableCacheMode;\n /** How long an entry stays fresh. */\n maxAge?: CacheDuration;\n /** How long after the fresh window the cache serves a stale entry while it refreshes the entry in the background. */\n staleWhileRevalidate?: CacheDuration;\n /**\n * How long after the stale-while-revalidate window the cache serves a stale entry when a refresh fails.\n */\n staleIfError?: CacheDuration;\n};\n\n/** A strategy that skips the cache. */\ntype NoStoreStrategy = CachingStrategy & {\n mode: typeof NO_STORE;\n};\n\n/**\n * Creates caching strategies for Storefront API queries, cached fetches, and cached runs. Call\n * `Cache` with options for a custom strategy, or use the `none`, `short`, and `long` presets.\n *\n * @publicDocs\n */\nexport const Cache = Object.assign(createCache, {\n none: cacheNone,\n short: cacheShort,\n long: cacheLong,\n});\n\n/**\n * Creates caching strategies for Storefront API queries, cached fetches, and cached runs. Call `Cache` with options for a custom strategy, or use the `short`, `long`, and `none` presets.\n *\n * @publicDocs\n */\nexport type CacheForDocs = {\n (options: CacheOptions): CachingStrategy;\n /**\n * Returns a strategy that skips the cache. The preset takes no options.\n */\n none: typeof cacheNone;\n /**\n * Keeps public entries fresh for 1 second, then serves stale entries for 9 more seconds while the cache refreshes them. Pass options to override the mode or either duration, or to add `staleIfError`.\n */\n short: typeof cacheShort;\n /**\n * Keeps public entries fresh for 1 hour, then serves stale entries for 23 more hours while the cache refreshes them. Pass options to override the mode or either duration, or to add `staleIfError`.\n */\n long: typeof cacheLong;\n};\n\nexport function getCacheRetentionTtl(strategy: CachingStrategy): number {\n if (strategy.mode === NO_STORE) return 0;\n\n return Math.max(\n 0,\n (strategy.maxAge ?? 0) + (strategy.staleWhileRevalidate ?? 0) + (strategy.staleIfError ?? 0),\n );\n}\n\nexport function getPlatformCacheControlHeader(strategy: CachingStrategy): string {\n const ttl = getCacheRetentionTtl(strategy);\n if (ttl <= 0) return NO_STORE;\n\n return `${strategy.mode ?? PUBLIC}, max-age=${Math.ceil(ttl)}`;\n}\n\nfunction createCache(options: CacheOptions): CachingStrategy {\n return normalizeCacheOptions(options);\n}\n\nfunction cacheNone(): NoStoreStrategy {\n return { mode: NO_STORE };\n}\n\nfunction cacheShort(overrideOptions?: CacheOptions): CachingStrategy {\n return normalizeCacheOptions(\n {\n mode: PUBLIC,\n maxAge: 1,\n staleWhileRevalidate: 9,\n },\n overrideOptions,\n );\n}\n\nfunction cacheLong(overrideOptions?: CacheOptions): CachingStrategy {\n return normalizeCacheOptions(\n {\n mode: PUBLIC,\n maxAge: 3600,\n staleWhileRevalidate: 82800,\n },\n overrideOptions,\n );\n}\n\nfunction normalizeCacheOptions(\n defaults: CacheOptions,\n overrideOptions?: CacheOptions,\n): CachingStrategy;\nfunction normalizeCacheOptions(options: CacheOptions): CachingStrategy;\nfunction normalizeCacheOptions(\n defaultsOrOptions: CacheOptions,\n overrideOptions?: CacheOptions,\n): CachingStrategy {\n const options = {\n ...defaultsOrOptions,\n ...overrideOptions,\n };\n\n return {\n ...normalizeMode(options),\n ...normalizeDurationOption(\"maxAge\", options.maxAge),\n ...normalizeDurationOption(\"staleWhileRevalidate\", options.staleWhileRevalidate),\n ...normalizeDurationOption(\"staleIfError\", options.staleIfError),\n };\n}\n\nfunction normalizeMode(options: CacheOptions): Pick<CachingStrategy, \"mode\"> {\n if (options.mode && options.mode !== PUBLIC && options.mode !== PRIVATE) {\n throw new Error(\"'mode' must be either 'public' or 'private'\");\n }\n\n return { mode: options.mode ?? PUBLIC };\n}\n\nfunction normalizeDurationOption(\n name: \"maxAge\" | \"staleWhileRevalidate\" | \"staleIfError\",\n duration: CacheDuration | undefined,\n): Pick<CachingStrategy, typeof name> {\n if (duration == null) return {};\n\n return { [name]: normalizeDuration(duration) };\n}\n\nfunction normalizeDuration(duration: CacheDuration): number {\n if (typeof duration === \"number\") return validateDuration(duration);\n\n return validateDuration(\n (duration.seconds ?? 0) +\n (duration.minutes ?? 0) * 60 +\n (duration.hours ?? 0) * 3600 +\n (duration.days ?? 0) * 86400,\n );\n}\n\nfunction validateDuration(duration: number): number {\n if (!Number.isFinite(duration) || duration < 0) {\n throw new Error(\"Cache durations must be finite, non-negative numbers.\");\n }\n\n return Math.ceil(duration);\n}\n"],"mappings":";AAAA,MAAa,WAAW;AAExB,MAAM,SAAS;AACf,MAAM,UAAU;;;;;;;AA4DhB,MAAa,QAAQ,OAAO,OAAO,aAAa;CAC9C,MAAM;CACN,OAAO;CACP,MAAM;CACP,CAAC;AAuBF,SAAgB,qBAAqB,UAAmC;AACtE,KAAI,SAAS,oBAAmB,QAAO;AAEvC,QAAO,KAAK,IACV,IACC,SAAS,UAAU,MAAM,SAAS,wBAAwB,MAAM,SAAS,gBAAgB,GAC3F;;AAGH,SAAgB,8BAA8B,UAAmC;CAC/E,MAAM,MAAM,qBAAqB,SAAS;AAC1C,KAAI,OAAO,EAAG,QAAO;AAErB,QAAO,GAAG,SAAS,QAAQ,OAAO,YAAY,KAAK,KAAK,IAAI;;AAG9D,SAAS,YAAY,SAAwC;AAC3D,QAAO,sBAAsB,QAAQ;;AAGvC,SAAS,YAA6B;AACpC,QAAO,EAAE,MAAM,UAAU;;AAG3B,SAAS,WAAW,iBAAiD;AACnE,QAAO,sBACL;EACE,MAAM;EACN,QAAQ;EACR,sBAAsB;EACvB,EACD,gBACD;;AAGH,SAAS,UAAU,iBAAiD;AAClE,QAAO,sBACL;EACE,MAAM;EACN,QAAQ;EACR,sBAAsB;EACvB,EACD,gBACD;;AAQH,SAAS,sBACP,mBACA,iBACiB;CACjB,MAAM,UAAU;EACd,GAAG;EACH,GAAG;EACJ;AAED,QAAO;EACL,GAAG,cAAc,QAAQ;EACzB,GAAG,wBAAwB,UAAU,QAAQ,OAAO;EACpD,GAAG,wBAAwB,wBAAwB,QAAQ,qBAAqB;EAChF,GAAG,wBAAwB,gBAAgB,QAAQ,aAAa;EACjE;;AAGH,SAAS,cAAc,SAAsD;AAC3E,KAAI,QAAQ,QAAQ,QAAQ,SAAS,UAAU,QAAQ,SAAS,QAC9D,OAAM,IAAI,MAAM,8CAA8C;AAGhE,QAAO,EAAE,MAAM,QAAQ,QAAQ,QAAQ;;AAGzC,SAAS,wBACP,MACA,UACoC;AACpC,KAAI,YAAY,KAAM,QAAO,EAAE;AAE/B,QAAO,GAAG,OAAO,kBAAkB,SAAS,EAAE;;AAGhD,SAAS,kBAAkB,UAAiC;AAC1D,KAAI,OAAO,aAAa,SAAU,QAAO,iBAAiB,SAAS;AAEnE,QAAO,kBACJ,SAAS,WAAW,MAClB,SAAS,WAAW,KAAK,MACzB,SAAS,SAAS,KAAK,QACvB,SAAS,QAAQ,KAAK,MAC1B;;AAGH,SAAS,iBAAiB,UAA0B;AAClD,KAAI,CAAC,OAAO,SAAS,SAAS,IAAI,WAAW,EAC3C,OAAM,IAAI,MAAM,wDAAwD;AAG1E,QAAO,KAAK,KAAK,SAAS"}
|
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
//#region src/core/cart/actions.d.ts
|
|
2
|
-
/** A key-value pair
|
|
2
|
+
/** A custom key-value pair for the cart or a cart line. */
|
|
3
3
|
type CartAttributeInput = {
|
|
4
|
-
key: string;
|
|
4
|
+
/** The attribute name. parseCartRequest throws for an empty name in cart attributes and in form submissions. */key: string; /** The attribute value. */
|
|
5
5
|
value: string;
|
|
6
6
|
};
|
|
7
|
+
/** Input for adding a line to the cart. */
|
|
7
8
|
type CartLineAddInput = {
|
|
8
|
-
/** Storefront API GID of the product variant to add. */merchandiseId: string;
|
|
9
|
-
quantity: number;
|
|
10
|
-
attributes?: CartAttributeInput[]; /**
|
|
9
|
+
/** The Storefront API GID of the product variant to add. */merchandiseId: string; /** Number of units to add. A form submission without a quantity adds `1`. */
|
|
10
|
+
quantity: number; /** Custom key-value attributes to set on the new line. */
|
|
11
|
+
attributes?: CartAttributeInput[]; /** The selling plan GID for a subscription line. */
|
|
11
12
|
sellingPlanId?: string;
|
|
12
13
|
};
|
|
13
|
-
/** Input for updating an existing cart line.
|
|
14
|
+
/** Input for updating an existing cart line. A quantity of `0` removes the line. */
|
|
14
15
|
type CartLineUpdateInput = {
|
|
15
|
-
/** The
|
|
16
|
-
quantity
|
|
17
|
-
attributes
|
|
16
|
+
/** The ID of the cart line to update. */id: string; /** The GID of a product variant to swap the line to. */
|
|
17
|
+
merchandiseId?: string; /** The line's new quantity. */
|
|
18
|
+
quantity: number; /** Custom key-value attributes to set on the line. */
|
|
19
|
+
attributes?: CartAttributeInput[]; /** The selling plan GID to assign to the line for a subscription. */
|
|
18
20
|
sellingPlanId?: string;
|
|
19
21
|
};
|
|
20
22
|
/**
|
|
21
|
-
*
|
|
23
|
+
* A cart change that `parseCartRequest` reads from a JSON or form request.
|
|
22
24
|
*
|
|
23
|
-
*
|
|
24
|
-
* of these variants. The `intent` field determines the Storefront API mutation:
|
|
25
|
+
* The `intent` field names the change and the Storefront API mutation that applies the change:
|
|
25
26
|
*
|
|
26
|
-
* - `
|
|
27
|
-
* - `
|
|
28
|
-
* - `
|
|
29
|
-
* - `
|
|
30
|
-
* - `
|
|
31
|
-
* - `
|
|
32
|
-
* - `
|
|
33
|
-
* - `
|
|
27
|
+
* - `add` adds new lines with `cartLinesAdd`, or creates a cart with `cartCreate` when none exists.
|
|
28
|
+
* - `update` changes quantity, merchandise, or attributes on existing lines with `cartLinesUpdate`.
|
|
29
|
+
* - `remove` removes lines by ID with `cartLinesRemove`.
|
|
30
|
+
* - `discount-update` replaces all discount codes with `cartDiscountCodesUpdate`.
|
|
31
|
+
* - `discount-apply` adds one discount code. The handler reads the current codes, then writes the new list with `cartDiscountCodesUpdate`.
|
|
32
|
+
* - `discount-remove` removes one discount code with the same read-then-write approach.
|
|
33
|
+
* - `attributes-update` sets cart-level attributes with `cartAttributesUpdate`.
|
|
34
|
+
* - `note-update` sets the cart note with `cartNoteUpdate`.
|
|
35
|
+
*
|
|
36
|
+
* @publicDocs
|
|
34
37
|
*/
|
|
35
38
|
type CartAction = {
|
|
36
39
|
intent: "add";
|
|
@@ -57,23 +60,26 @@ type CartAction = {
|
|
|
57
60
|
intent: "note-update";
|
|
58
61
|
note: string;
|
|
59
62
|
};
|
|
63
|
+
/** The cart change and the cart ID that `parseCartRequest` reads from the request body. */
|
|
60
64
|
type ParsedCartRequest = {
|
|
61
|
-
action: CartAction;
|
|
65
|
+
action: CartAction; /** The cart GID from a JSON body, or `null` for a form submission or a JSON body without a cart ID. The function adds the `gid://shopify/Cart/` prefix to a bare cart token. */
|
|
62
66
|
cartId: string | null;
|
|
63
67
|
};
|
|
64
68
|
/**
|
|
65
|
-
*
|
|
69
|
+
* Reads the cart change from a cart request body. Use the function in a custom cart route.
|
|
70
|
+
*
|
|
71
|
+
* The function accepts `application/json`, `application/x-www-form-urlencoded`, and
|
|
72
|
+
* `multipart/form-data` requests. A JSON request can replace every discount code, and a form submission applies or removes one code.
|
|
66
73
|
*
|
|
67
|
-
*
|
|
68
|
-
* `multipart/form-data` content types. JSON and FormData each support a
|
|
69
|
-
* different subset of intents — JSON produces `discount-update` while
|
|
70
|
-
* FormData produces `discount-apply` / `discount-remove`.
|
|
74
|
+
* A form submission always returns a `null` cart ID. Read the cart ID for a form submission with `getCartId`.
|
|
71
75
|
*
|
|
72
|
-
*
|
|
73
|
-
* should fall back to the cart cookie.
|
|
76
|
+
* The function reads the request body, and you can't read the body again afterward. The function throws when the request has an unsupported content type, an unknown intent, or a missing required field, and when a JSON body combines more than one of `lines`, `discountCodes`, `attributes`, and `note`.
|
|
74
77
|
*
|
|
75
|
-
* @
|
|
76
|
-
*
|
|
78
|
+
* @param request The incoming cart request.
|
|
79
|
+
* @returns The cart change and, for a JSON body, the cart ID.
|
|
80
|
+
* @throws If the request has an unsupported content type, an unrecognized intent,
|
|
81
|
+
* or missing required fields, or if a JSON body combines more than one of
|
|
82
|
+
* `lines`, `discountCodes`, `attributes`, and `note`.
|
|
77
83
|
*
|
|
78
84
|
* @example
|
|
79
85
|
* ```ts
|
|
@@ -87,6 +93,7 @@ type ParsedCartRequest = {
|
|
|
87
93
|
* // ...
|
|
88
94
|
* }
|
|
89
95
|
* ```
|
|
96
|
+
* @publicDocs
|
|
90
97
|
*/
|
|
91
98
|
declare function parseCartRequest(request: Request): Promise<ParsedCartRequest>;
|
|
92
99
|
//#endregion
|
|
@@ -9,18 +9,20 @@ var CartActionError = class extends Error {
|
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Reads the cart change from a cart request body. Use the function in a custom cart route.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* `multipart/form-data`
|
|
16
|
-
* different subset of intents — JSON produces `discount-update` while
|
|
17
|
-
* FormData produces `discount-apply` / `discount-remove`.
|
|
14
|
+
* The function accepts `application/json`, `application/x-www-form-urlencoded`, and
|
|
15
|
+
* `multipart/form-data` requests. A JSON request can replace every discount code, and a form submission applies or removes one code.
|
|
18
16
|
*
|
|
19
|
-
*
|
|
20
|
-
* should fall back to the cart cookie.
|
|
17
|
+
* A form submission always returns a `null` cart ID. Read the cart ID for a form submission with `getCartId`.
|
|
21
18
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* The function reads the request body, and you can't read the body again afterward. The function throws when the request has an unsupported content type, an unknown intent, or a missing required field, and when a JSON body combines more than one of `lines`, `discountCodes`, `attributes`, and `note`.
|
|
20
|
+
*
|
|
21
|
+
* @param request The incoming cart request.
|
|
22
|
+
* @returns The cart change and, for a JSON body, the cart ID.
|
|
23
|
+
* @throws If the request has an unsupported content type, an unrecognized intent,
|
|
24
|
+
* or missing required fields, or if a JSON body combines more than one of
|
|
25
|
+
* `lines`, `discountCodes`, `attributes`, and `note`.
|
|
24
26
|
*
|
|
25
27
|
* @example
|
|
26
28
|
* ```ts
|
|
@@ -34,6 +36,7 @@ var CartActionError = class extends Error {
|
|
|
34
36
|
* // ...
|
|
35
37
|
* }
|
|
36
38
|
* ```
|
|
39
|
+
* @publicDocs
|
|
37
40
|
*/
|
|
38
41
|
async function parseCartRequest(request) {
|
|
39
42
|
const contentType = request.headers.get("content-type") ?? "";
|
|
@@ -44,9 +47,17 @@ async function parseCartRequest(request) {
|
|
|
44
47
|
};
|
|
45
48
|
throw new CartActionError(`Unsupported content-type: "${contentType}". Expected application/json or form data.`);
|
|
46
49
|
}
|
|
50
|
+
const JSON_MUTATION_FIELDS = [
|
|
51
|
+
"lines",
|
|
52
|
+
"discountCodes",
|
|
53
|
+
"attributes",
|
|
54
|
+
"note"
|
|
55
|
+
];
|
|
47
56
|
function parseJsonBody(body) {
|
|
48
57
|
assertObject(body);
|
|
49
58
|
const cartId = typeof body.cartId === "string" ? normalizeCartId(body.cartId) : null;
|
|
59
|
+
const fields = JSON_MUTATION_FIELDS.filter((field) => body[field] !== void 0);
|
|
60
|
+
if (fields.length > 1) throw new CartActionError(`Request body must contain only one of "lines", "discountCodes", "attributes", or "note", got ${fields.join(", ")}.`);
|
|
50
61
|
if ("note" in body && typeof body.note === "string") return {
|
|
51
62
|
action: {
|
|
52
63
|
intent: "note-update",
|
|
@@ -101,6 +112,7 @@ function partitionLines(rawLines) {
|
|
|
101
112
|
else if (hasId && quantity === 0) removeIds.push(line.id);
|
|
102
113
|
else if (hasId) updates.push({
|
|
103
114
|
id: line.id,
|
|
115
|
+
...hasMerchandiseId && { merchandiseId: line.merchandiseId },
|
|
104
116
|
quantity,
|
|
105
117
|
...optionalFields
|
|
106
118
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.mjs","names":[],"sources":["../../../../src/core/cart/actions.ts"],"sourcesContent":["import { normalizeCartId } from \"./cookie\";\nimport { getCartAttributeFormEntries } from \"./form\";\n\n/** A key-value pair attached to the cart or an individual cart line. */\nexport type CartAttributeInput = { key: string; value: string };\n\nexport type CartLineAddInput = {\n /** Storefront API GID of the product variant to add. */\n merchandiseId: string;\n quantity: number;\n attributes?: CartAttributeInput[];\n /** Selling plan GID for subscription line items. */\n sellingPlanId?: string;\n};\n\n/** Input for updating an existing cart line. Setting `quantity` to `0` removes the line. */\nexport type CartLineUpdateInput = {\n /** The `CartLine.id` of the line to update. */\n id: string;\n quantity: number;\n attributes?: CartAttributeInput[];\n sellingPlanId?: string;\n};\n\n/**\n * Discriminated union of all cart mutation intents.\n *\n * {@link parseCartRequest} normalizes both JSON and FormData requests into one\n * of these variants. The `intent` field determines the Storefront API mutation:\n *\n * - `\"add\"` — add new lines (cartLinesAdd, or cartCreate when no cart exists)\n * - `\"update\"` — change quantity or attributes on existing lines (cartLinesUpdate)\n * - `\"remove\"` — remove lines by ID (cartLinesRemove)\n * - `\"discount-update\"` — replace all discount codes (cartDiscountCodesUpdate)\n * - `\"discount-apply\"` — add a single discount code (read-then-write via cartDiscountCodesUpdate)\n * - `\"discount-remove\"` — remove a single discount code (read-then-write via cartDiscountCodesUpdate)\n * - `\"attributes-update\"` — set cart-level attributes (cartAttributesUpdate)\n * - `\"note-update\"` — set the cart note (cartNoteUpdate)\n */\nexport type CartAction =\n | { intent: \"add\"; lines: CartLineAddInput[] }\n | { intent: \"update\"; lines: CartLineUpdateInput[] }\n | { intent: \"remove\"; lineIds: string[] }\n | { intent: \"discount-update\"; discountCodes: string[] }\n | { intent: \"discount-apply\"; code: string }\n | { intent: \"discount-remove\"; code: string }\n | { intent: \"attributes-update\"; attributes: CartAttributeInput[] }\n | { intent: \"note-update\"; note: string };\n\ntype ParsedCartRequest = {\n action: CartAction;\n cartId: string | null;\n};\n\nclass CartActionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CartActionError\";\n }\n}\n\n/**\n * Parses an incoming cart mutation request into a typed {@link CartAction}.\n *\n * Accepts `application/json` and `application/x-www-form-urlencoded` /\n * `multipart/form-data` content types. JSON and FormData each support a\n * different subset of intents — JSON produces `discount-update` while\n * FormData produces `discount-apply` / `discount-remove`.\n *\n * FormData requests always return `cartId: null` — the server handler\n * should fall back to the cart cookie.\n *\n * @throws If the content-type is unsupported, the intent is unrecognized,\n * or required fields are missing.\n *\n * @example\n * ```ts\n * const { action, cartId } = await parseCartRequest(request);\n *\n * switch (action.intent) {\n * case \"add\":\n * return cartLinesAdd(cartId ?? getCartId(request), action.lines);\n * case \"remove\":\n * return cartLinesRemove(cartId ?? getCartId(request), action.lineIds);\n * // ...\n * }\n * ```\n */\nexport async function parseCartRequest(request: Request): Promise<ParsedCartRequest> {\n const contentType = request.headers.get(\"content-type\") ?? \"\";\n\n if (contentType.includes(\"application/json\")) {\n return parseJsonBody(await request.json());\n }\n\n if (\n contentType.includes(\"application/x-www-form-urlencoded\") ||\n contentType.includes(\"multipart/form-data\")\n ) {\n return { action: parseFormData(await request.formData()), cartId: null };\n }\n\n throw new CartActionError(\n `Unsupported content-type: \"${contentType}\". Expected application/json or form data.`,\n );\n}\n\n// --- JSON parsing ---\n\nfunction parseJsonBody(body: unknown): ParsedCartRequest {\n assertObject(body);\n const cartId = typeof body.cartId === \"string\" ? normalizeCartId(body.cartId) : null;\n\n if (\"note\" in body && typeof body.note === \"string\") {\n return { action: { intent: \"note-update\", note: body.note }, cartId };\n }\n\n if (\"discountCodes\" in body && Array.isArray(body.discountCodes)) {\n for (const code of body.discountCodes) {\n assertString(code, \"discountCodes entries\");\n }\n return {\n action: { intent: \"discount-update\", discountCodes: body.discountCodes as string[] },\n cartId,\n };\n }\n\n if (\"attributes\" in body && Array.isArray(body.attributes)) {\n return {\n action: { intent: \"attributes-update\", attributes: parseAttributes(body.attributes) },\n cartId,\n };\n }\n\n if (!(\"lines\" in body) || !Array.isArray(body.lines)) {\n throw new CartActionError(\n 'Request body must contain \"lines\", \"discountCodes\", \"attributes\", or \"note\".',\n );\n }\n\n const lines = body.lines as unknown[];\n if (lines.length === 0) {\n throw new CartActionError(\"Lines array must not be empty.\");\n }\n\n return { action: partitionLines(lines), cartId };\n}\n\nfunction partitionLines(rawLines: unknown[]): CartAction {\n const adds: CartLineAddInput[] = [];\n const updates: CartLineUpdateInput[] = [];\n const removeIds: string[] = [];\n\n for (const raw of rawLines) {\n assertObject(raw);\n const line = raw as Record<string, unknown>;\n const hasId = \"id\" in line && typeof line.id === \"string\" && line.id !== \"\";\n const hasMerchandiseId =\n \"merchandiseId\" in line &&\n typeof line.merchandiseId === \"string\" &&\n line.merchandiseId !== \"\";\n\n assertNonNegativeInteger(line.quantity, \"quantity\");\n const quantity = line.quantity as number;\n\n if (!hasId && !hasMerchandiseId) {\n throw new CartActionError(\n 'Each line must have either \"id\" (for update/remove) or \"merchandiseId\" (for add).',\n );\n }\n\n if (hasId && (line.id as string) === \"\") {\n throw new CartActionError('Line \"id\" must not be empty.');\n }\n\n const optionalFields = extractOptionalLineFields(line);\n\n if (!hasId && hasMerchandiseId) {\n adds.push({\n merchandiseId: line.merchandiseId as string,\n quantity,\n ...optionalFields,\n });\n } else if (hasId && quantity === 0) {\n removeIds.push(line.id as string);\n } else if (hasId) {\n updates.push({\n id: line.id as string,\n quantity,\n ...optionalFields,\n });\n }\n }\n\n const populatedBuckets = [adds.length, updates.length, removeIds.length].filter(\n (n) => n > 0,\n ).length;\n\n if (populatedBuckets > 1) {\n throw new CartActionError(\n \"Mixed line operations are not allowed. Separate add, update, and remove into distinct requests.\",\n );\n }\n\n if (adds.length > 0) return { intent: \"add\", lines: adds };\n if (updates.length > 0) return { intent: \"update\", lines: updates };\n return { intent: \"remove\", lineIds: removeIds };\n}\n\nfunction extractOptionalLineFields(\n line: Record<string, unknown>,\n): Pick<CartLineAddInput, \"attributes\" | \"sellingPlanId\"> {\n const result: Pick<CartLineAddInput, \"attributes\" | \"sellingPlanId\"> = {};\n\n if (\"attributes\" in line && Array.isArray(line.attributes)) {\n result.attributes = line.attributes as CartAttributeInput[];\n }\n\n if (\"sellingPlanId\" in line && typeof line.sellingPlanId === \"string\") {\n result.sellingPlanId = line.sellingPlanId;\n }\n\n return result;\n}\n\n// --- FormData parsing ---\n//\n// HTML forms can't express the same structures as JSON. Key compromises:\n//\n// Add line: single item per submission (forms can't batch multiple adds)\n// Update qty: server does the math — form sends current qty, server increments/decrements\n// Remove line: explicit intent field (can't express \"quantity=0 means remove\" implicitly)\n// Discount bulk: no FormData equivalent — forms handle one code at a time\n// Cart attributes: attributes.<key> fields preserve each key/value relationship\n// Selling plan: add only — no way to change selling plan on existing line via form\n//\n// Design decisions:\n// - `intent` field disambiguates operations (forms need explicit routing; JSON infers from structure)\n// - Field names match where possible: merchandiseId, quantity, lineId, discountCode, note\n// - Form submissions get 303 redirect to referer; JSON gets JSON response\n// - `increase`/`decrease` intents exist because forms can't atomically read-modify-write quantity\n\nconst LINE_INTENTS = new Set([\"increase\", \"decrease\", \"remove\", \"set\"]);\nconst DISCOUNT_INTENTS = new Set([\"discount-apply\", \"discount-remove\"]);\n\nfunction parseFormData(form: FormData): CartAction {\n const intent = formString(form, \"intent\");\n const merchandiseId = formString(form, \"merchandiseId\");\n\n if (!intent && merchandiseId) {\n return parseAddIntent(form, merchandiseId);\n }\n\n if (intent === \"add\") {\n if (!merchandiseId) {\n throw new CartActionError('Intent \"add\" requires a \"merchandiseId\" field.');\n }\n return parseAddIntent(form, merchandiseId);\n }\n\n if (!intent) {\n throw new CartActionError(\n 'FormData must include an \"intent\" field or a \"merchandiseId\" field.',\n );\n }\n\n if (LINE_INTENTS.has(intent)) {\n return parseLineIntent(intent, form);\n }\n\n if (DISCOUNT_INTENTS.has(intent)) {\n return parseDiscountIntent(intent as \"discount-apply\" | \"discount-remove\", form);\n }\n\n if (intent === \"note-update\") {\n return parseNoteIntent(form);\n }\n\n if (intent === \"attributes-update\") {\n return parseAttributesIntent(form);\n }\n\n throw new CartActionError(\n `Unknown intent \"${intent}\". Expected one of: add, increase, decrease, remove, set, discount-apply, discount-remove, attributes-update, note-update.`,\n );\n}\n\nfunction parseLineIntent(intent: string, form: FormData): CartAction {\n const lineId = formString(form, \"lineId\");\n if (!lineId) {\n throw new CartActionError(`Intent \"${intent}\" requires a \"lineId\" field.`);\n }\n\n if (intent === \"remove\") {\n return { intent: \"remove\", lineIds: [lineId] };\n }\n\n const quantity = formInt(form, \"quantity\");\n if (quantity === null) {\n throw new CartActionError(`Intent \"${intent}\" requires a \"quantity\" field.`);\n }\n\n if (intent === \"set\") {\n if (quantity <= 0) {\n return { intent: \"remove\", lineIds: [lineId] };\n }\n return { intent: \"update\", lines: [{ id: lineId, quantity }] };\n }\n\n if (intent === \"increase\") {\n return { intent: \"update\", lines: [{ id: lineId, quantity: quantity + 1 }] };\n }\n\n const decremented = quantity - 1;\n if (decremented <= 0) {\n return { intent: \"remove\", lineIds: [lineId] };\n }\n return { intent: \"update\", lines: [{ id: lineId, quantity: decremented }] };\n}\n\nfunction parseDiscountIntent(\n intent: \"discount-apply\" | \"discount-remove\",\n form: FormData,\n): CartAction {\n const code = formString(form, \"discountCode\");\n if (!code) {\n throw new CartActionError(`Intent \"${intent}\" requires a \"discountCode\" field.`);\n }\n return { intent, code };\n}\n\nfunction parseAddIntent(form: FormData, merchandiseId: string): CartAction {\n const quantity = formInt(form, \"quantity\") ?? 1;\n const sellingPlanId = formString(form, \"sellingPlanId\");\n\n const line: CartLineAddInput = { merchandiseId, quantity };\n if (sellingPlanId) line.sellingPlanId = sellingPlanId;\n\n const rawAttributes = getCartAttributeFormEntries(form);\n if (rawAttributes.length > 0) {\n line.attributes = parseAttributes(rawAttributes);\n }\n\n return { intent: \"add\", lines: [line] };\n}\n\nfunction parseNoteIntent(form: FormData): CartAction {\n const noteValue = form.get(\"note\");\n if (noteValue === null) {\n throw new CartActionError('Intent \"note-update\" requires a \"note\" field.');\n }\n return { intent: \"note-update\", note: String(noteValue) };\n}\n\nfunction parseAttributesIntent(form: FormData): CartAction {\n return {\n intent: \"attributes-update\",\n attributes: parseAttributes(getCartAttributeFormEntries(form)),\n };\n}\n\nfunction parseAttributes(values: unknown[]): CartAttributeInput[] {\n return values.map((value, index) => {\n assertObject(value);\n assertString(value.key, `attributes[${index}].key`);\n assertString(value.value, `attributes[${index}].value`);\n if (!value.key) {\n throw new CartActionError(`Expected \"attributes[${index}].key\" not to be empty.`);\n }\n return { key: value.key, value: value.value };\n });\n}\n\n// --- Assertion helpers ---\n\nfunction assertObject(value: unknown): asserts value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new CartActionError(\"Expected an object.\");\n }\n}\n\nfunction assertString(value: unknown, label: string): asserts value is string {\n if (typeof value !== \"string\") {\n throw new CartActionError(`Expected \"${label}\" to be a string.`);\n }\n}\n\nfunction assertNonNegativeInteger(value: unknown, label: string): asserts value is number {\n if (typeof value !== \"number\" || !Number.isInteger(value) || value < 0) {\n throw new CartActionError(\n `Expected \"${label}\" to be a non-negative integer, got ${JSON.stringify(value)}.`,\n );\n }\n}\n\n// --- FormData field helpers ---\n\nfunction formString(form: FormData, key: string): string | null {\n const value = form.get(key);\n if (value === null || typeof value !== \"string\") return null;\n return value || null;\n}\n\nfunction formInt(form: FormData, key: string): number | null {\n const raw = form.get(key);\n if (raw === null || typeof raw !== \"string\") return null;\n const parsed = parseInt(raw, 10);\n if (Number.isNaN(parsed)) return null;\n return parsed;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"actions.mjs","names":[],"sources":["../../../../src/core/cart/actions.ts"],"sourcesContent":["import { normalizeCartId } from \"./cookie\";\nimport { getCartAttributeFormEntries } from \"./form\";\n\n/** A custom key-value pair for the cart or a cart line. */\nexport type CartAttributeInput = {\n /** The attribute name. parseCartRequest throws for an empty name in cart attributes and in form submissions. */\n key: string;\n /** The attribute value. */\n value: string;\n};\n\n/** Input for adding a line to the cart. */\nexport type CartLineAddInput = {\n /** The Storefront API GID of the product variant to add. */\n merchandiseId: string;\n /** Number of units to add. A form submission without a quantity adds `1`. */\n quantity: number;\n /** Custom key-value attributes to set on the new line. */\n attributes?: CartAttributeInput[];\n /** The selling plan GID for a subscription line. */\n sellingPlanId?: string;\n};\n\n/** Input for updating an existing cart line. A quantity of `0` removes the line. */\nexport type CartLineUpdateInput = {\n /** The ID of the cart line to update. */\n id: string;\n /** The GID of a product variant to swap the line to. */\n merchandiseId?: string;\n /** The line's new quantity. */\n quantity: number;\n /** Custom key-value attributes to set on the line. */\n attributes?: CartAttributeInput[];\n /** The selling plan GID to assign to the line for a subscription. */\n sellingPlanId?: string;\n};\n\n/**\n * A cart change that `parseCartRequest` reads from a JSON or form request.\n *\n * The `intent` field names the change and the Storefront API mutation that applies the change:\n *\n * - `add` adds new lines with `cartLinesAdd`, or creates a cart with `cartCreate` when none exists.\n * - `update` changes quantity, merchandise, or attributes on existing lines with `cartLinesUpdate`.\n * - `remove` removes lines by ID with `cartLinesRemove`.\n * - `discount-update` replaces all discount codes with `cartDiscountCodesUpdate`.\n * - `discount-apply` adds one discount code. The handler reads the current codes, then writes the new list with `cartDiscountCodesUpdate`.\n * - `discount-remove` removes one discount code with the same read-then-write approach.\n * - `attributes-update` sets cart-level attributes with `cartAttributesUpdate`.\n * - `note-update` sets the cart note with `cartNoteUpdate`.\n *\n * @publicDocs\n */\nexport type CartAction =\n | { intent: \"add\"; lines: CartLineAddInput[] }\n | { intent: \"update\"; lines: CartLineUpdateInput[] }\n | { intent: \"remove\"; lineIds: string[] }\n | { intent: \"discount-update\"; discountCodes: string[] }\n | { intent: \"discount-apply\"; code: string }\n | { intent: \"discount-remove\"; code: string }\n | { intent: \"attributes-update\"; attributes: CartAttributeInput[] }\n | { intent: \"note-update\"; note: string };\n\n/** The cart change and the cart ID that `parseCartRequest` reads from the request body. */\ntype ParsedCartRequest = {\n action: CartAction;\n /** The cart GID from a JSON body, or `null` for a form submission or a JSON body without a cart ID. The function adds the `gid://shopify/Cart/` prefix to a bare cart token. */\n cartId: string | null;\n};\n\nclass CartActionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CartActionError\";\n }\n}\n\n/**\n * Reads the cart change from a cart request body. Use the function in a custom cart route.\n *\n * The function accepts `application/json`, `application/x-www-form-urlencoded`, and\n * `multipart/form-data` requests. A JSON request can replace every discount code, and a form submission applies or removes one code.\n *\n * A form submission always returns a `null` cart ID. Read the cart ID for a form submission with `getCartId`.\n *\n * The function reads the request body, and you can't read the body again afterward. The function throws when the request has an unsupported content type, an unknown intent, or a missing required field, and when a JSON body combines more than one of `lines`, `discountCodes`, `attributes`, and `note`.\n *\n * @param request The incoming cart request.\n * @returns The cart change and, for a JSON body, the cart ID.\n * @throws If the request has an unsupported content type, an unrecognized intent,\n * or missing required fields, or if a JSON body combines more than one of\n * `lines`, `discountCodes`, `attributes`, and `note`.\n *\n * @example\n * ```ts\n * const { action, cartId } = await parseCartRequest(request);\n *\n * switch (action.intent) {\n * case \"add\":\n * return cartLinesAdd(cartId ?? getCartId(request), action.lines);\n * case \"remove\":\n * return cartLinesRemove(cartId ?? getCartId(request), action.lineIds);\n * // ...\n * }\n * ```\n * @publicDocs\n */\nexport async function parseCartRequest(request: Request): Promise<ParsedCartRequest> {\n const contentType = request.headers.get(\"content-type\") ?? \"\";\n\n if (contentType.includes(\"application/json\")) {\n return parseJsonBody(await request.json());\n }\n\n if (\n contentType.includes(\"application/x-www-form-urlencoded\") ||\n contentType.includes(\"multipart/form-data\")\n ) {\n return { action: parseFormData(await request.formData()), cartId: null };\n }\n\n throw new CartActionError(\n `Unsupported content-type: \"${contentType}\". Expected application/json or form data.`,\n );\n}\n\n// --- JSON parsing ---\n\nconst JSON_MUTATION_FIELDS = [\"lines\", \"discountCodes\", \"attributes\", \"note\"] as const;\n\nfunction parseJsonBody(body: unknown): ParsedCartRequest {\n assertObject(body);\n const cartId = typeof body.cartId === \"string\" ? normalizeCartId(body.cartId) : null;\n\n // Each request runs one Storefront API mutation, so any other field would be silently dropped.\n const fields = JSON_MUTATION_FIELDS.filter((field) => body[field] !== undefined);\n if (fields.length > 1) {\n throw new CartActionError(\n `Request body must contain only one of \"lines\", \"discountCodes\", \"attributes\", or \"note\", got ${fields.join(\", \")}.`,\n );\n }\n\n if (\"note\" in body && typeof body.note === \"string\") {\n return { action: { intent: \"note-update\", note: body.note }, cartId };\n }\n\n if (\"discountCodes\" in body && Array.isArray(body.discountCodes)) {\n for (const code of body.discountCodes) {\n assertString(code, \"discountCodes entries\");\n }\n return {\n action: { intent: \"discount-update\", discountCodes: body.discountCodes as string[] },\n cartId,\n };\n }\n\n if (\"attributes\" in body && Array.isArray(body.attributes)) {\n return {\n action: { intent: \"attributes-update\", attributes: parseAttributes(body.attributes) },\n cartId,\n };\n }\n\n if (!(\"lines\" in body) || !Array.isArray(body.lines)) {\n throw new CartActionError(\n 'Request body must contain \"lines\", \"discountCodes\", \"attributes\", or \"note\".',\n );\n }\n\n const lines = body.lines as unknown[];\n if (lines.length === 0) {\n throw new CartActionError(\"Lines array must not be empty.\");\n }\n\n return { action: partitionLines(lines), cartId };\n}\n\nfunction partitionLines(rawLines: unknown[]): CartAction {\n const adds: CartLineAddInput[] = [];\n const updates: CartLineUpdateInput[] = [];\n const removeIds: string[] = [];\n\n for (const raw of rawLines) {\n assertObject(raw);\n const line = raw as Record<string, unknown>;\n const hasId = \"id\" in line && typeof line.id === \"string\" && line.id !== \"\";\n const hasMerchandiseId =\n \"merchandiseId\" in line &&\n typeof line.merchandiseId === \"string\" &&\n line.merchandiseId !== \"\";\n\n assertNonNegativeInteger(line.quantity, \"quantity\");\n const quantity = line.quantity as number;\n\n if (!hasId && !hasMerchandiseId) {\n throw new CartActionError(\n 'Each line must have either \"id\" (for update/remove) or \"merchandiseId\" (for add).',\n );\n }\n\n if (hasId && (line.id as string) === \"\") {\n throw new CartActionError('Line \"id\" must not be empty.');\n }\n\n const optionalFields = extractOptionalLineFields(line);\n\n if (!hasId && hasMerchandiseId) {\n adds.push({\n merchandiseId: line.merchandiseId as string,\n quantity,\n ...optionalFields,\n });\n } else if (hasId && quantity === 0) {\n removeIds.push(line.id as string);\n } else if (hasId) {\n updates.push({\n id: line.id as string,\n ...(hasMerchandiseId && { merchandiseId: line.merchandiseId as string }),\n quantity,\n ...optionalFields,\n });\n }\n }\n\n const populatedBuckets = [adds.length, updates.length, removeIds.length].filter(\n (n) => n > 0,\n ).length;\n\n if (populatedBuckets > 1) {\n throw new CartActionError(\n \"Mixed line operations are not allowed. Separate add, update, and remove into distinct requests.\",\n );\n }\n\n if (adds.length > 0) return { intent: \"add\", lines: adds };\n if (updates.length > 0) return { intent: \"update\", lines: updates };\n return { intent: \"remove\", lineIds: removeIds };\n}\n\nfunction extractOptionalLineFields(\n line: Record<string, unknown>,\n): Pick<CartLineAddInput, \"attributes\" | \"sellingPlanId\"> {\n const result: Pick<CartLineAddInput, \"attributes\" | \"sellingPlanId\"> = {};\n\n if (\"attributes\" in line && Array.isArray(line.attributes)) {\n result.attributes = line.attributes as CartAttributeInput[];\n }\n\n if (\"sellingPlanId\" in line && typeof line.sellingPlanId === \"string\") {\n result.sellingPlanId = line.sellingPlanId;\n }\n\n return result;\n}\n\n// --- FormData parsing ---\n//\n// HTML forms can't express the same structures as JSON. Key compromises:\n//\n// Add line: single item per submission (forms can't batch multiple adds)\n// Update qty: server does the math — form sends current qty, server increments/decrements\n// Remove line: explicit intent field (can't express \"quantity=0 means remove\" implicitly)\n// Discount bulk: no FormData equivalent — forms handle one code at a time\n// Cart attributes: attributes.<key> fields preserve each key/value relationship\n// Selling plan: add only — no way to change selling plan on existing line via form\n//\n// Design decisions:\n// - `intent` field disambiguates operations (forms need explicit routing; JSON infers from structure)\n// - Field names match where possible: merchandiseId, quantity, lineId, discountCode, note\n// - Form submissions get 303 redirect to referer; JSON gets JSON response\n// - `increase`/`decrease` intents exist because forms can't atomically read-modify-write quantity\n\nconst LINE_INTENTS = new Set([\"increase\", \"decrease\", \"remove\", \"set\"]);\nconst DISCOUNT_INTENTS = new Set([\"discount-apply\", \"discount-remove\"]);\n\nfunction parseFormData(form: FormData): CartAction {\n const intent = formString(form, \"intent\");\n const merchandiseId = formString(form, \"merchandiseId\");\n\n if (!intent && merchandiseId) {\n return parseAddIntent(form, merchandiseId);\n }\n\n if (intent === \"add\") {\n if (!merchandiseId) {\n throw new CartActionError('Intent \"add\" requires a \"merchandiseId\" field.');\n }\n return parseAddIntent(form, merchandiseId);\n }\n\n if (!intent) {\n throw new CartActionError(\n 'FormData must include an \"intent\" field or a \"merchandiseId\" field.',\n );\n }\n\n if (LINE_INTENTS.has(intent)) {\n return parseLineIntent(intent, form);\n }\n\n if (DISCOUNT_INTENTS.has(intent)) {\n return parseDiscountIntent(intent as \"discount-apply\" | \"discount-remove\", form);\n }\n\n if (intent === \"note-update\") {\n return parseNoteIntent(form);\n }\n\n if (intent === \"attributes-update\") {\n return parseAttributesIntent(form);\n }\n\n throw new CartActionError(\n `Unknown intent \"${intent}\". Expected one of: add, increase, decrease, remove, set, discount-apply, discount-remove, attributes-update, note-update.`,\n );\n}\n\nfunction parseLineIntent(intent: string, form: FormData): CartAction {\n const lineId = formString(form, \"lineId\");\n if (!lineId) {\n throw new CartActionError(`Intent \"${intent}\" requires a \"lineId\" field.`);\n }\n\n if (intent === \"remove\") {\n return { intent: \"remove\", lineIds: [lineId] };\n }\n\n const quantity = formInt(form, \"quantity\");\n if (quantity === null) {\n throw new CartActionError(`Intent \"${intent}\" requires a \"quantity\" field.`);\n }\n\n if (intent === \"set\") {\n if (quantity <= 0) {\n return { intent: \"remove\", lineIds: [lineId] };\n }\n return { intent: \"update\", lines: [{ id: lineId, quantity }] };\n }\n\n if (intent === \"increase\") {\n return { intent: \"update\", lines: [{ id: lineId, quantity: quantity + 1 }] };\n }\n\n const decremented = quantity - 1;\n if (decremented <= 0) {\n return { intent: \"remove\", lineIds: [lineId] };\n }\n return { intent: \"update\", lines: [{ id: lineId, quantity: decremented }] };\n}\n\nfunction parseDiscountIntent(\n intent: \"discount-apply\" | \"discount-remove\",\n form: FormData,\n): CartAction {\n const code = formString(form, \"discountCode\");\n if (!code) {\n throw new CartActionError(`Intent \"${intent}\" requires a \"discountCode\" field.`);\n }\n return { intent, code };\n}\n\nfunction parseAddIntent(form: FormData, merchandiseId: string): CartAction {\n const quantity = formInt(form, \"quantity\") ?? 1;\n const sellingPlanId = formString(form, \"sellingPlanId\");\n\n const line: CartLineAddInput = { merchandiseId, quantity };\n if (sellingPlanId) line.sellingPlanId = sellingPlanId;\n\n const rawAttributes = getCartAttributeFormEntries(form);\n if (rawAttributes.length > 0) {\n line.attributes = parseAttributes(rawAttributes);\n }\n\n return { intent: \"add\", lines: [line] };\n}\n\nfunction parseNoteIntent(form: FormData): CartAction {\n const noteValue = form.get(\"note\");\n if (noteValue === null) {\n throw new CartActionError('Intent \"note-update\" requires a \"note\" field.');\n }\n return { intent: \"note-update\", note: String(noteValue) };\n}\n\nfunction parseAttributesIntent(form: FormData): CartAction {\n return {\n intent: \"attributes-update\",\n attributes: parseAttributes(getCartAttributeFormEntries(form)),\n };\n}\n\nfunction parseAttributes(values: unknown[]): CartAttributeInput[] {\n return values.map((value, index) => {\n assertObject(value);\n assertString(value.key, `attributes[${index}].key`);\n assertString(value.value, `attributes[${index}].value`);\n if (!value.key) {\n throw new CartActionError(`Expected \"attributes[${index}].key\" not to be empty.`);\n }\n return { key: value.key, value: value.value };\n });\n}\n\n// --- Assertion helpers ---\n\nfunction assertObject(value: unknown): asserts value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new CartActionError(\"Expected an object.\");\n }\n}\n\nfunction assertString(value: unknown, label: string): asserts value is string {\n if (typeof value !== \"string\") {\n throw new CartActionError(`Expected \"${label}\" to be a string.`);\n }\n}\n\nfunction assertNonNegativeInteger(value: unknown, label: string): asserts value is number {\n if (typeof value !== \"number\" || !Number.isInteger(value) || value < 0) {\n throw new CartActionError(\n `Expected \"${label}\" to be a non-negative integer, got ${JSON.stringify(value)}.`,\n );\n }\n}\n\n// --- FormData field helpers ---\n\nfunction formString(form: FormData, key: string): string | null {\n const value = form.get(key);\n if (value === null || typeof value !== \"string\") return null;\n return value || null;\n}\n\nfunction formInt(form: FormData, key: string): number | null {\n const raw = form.get(key);\n if (raw === null || typeof raw !== \"string\") return null;\n const parsed = parseInt(raw, 10);\n if (Number.isNaN(parsed)) return null;\n return parsed;\n}\n"],"mappings":"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"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
//#region src/core/cart/attach-quantity-input.d.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Submits a cart form when the customer changes the form's quantity input.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* first submit button (which must carry `name="intent" value="set"`). Returns
|
|
7
|
-
* a cleanup function that removes the event listener.
|
|
5
|
+
* Make the form's first submit button the set button from `register("set")`. The function throws when the first submit button is any other button.
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
* `register("set")` from `createCartFormRegister` to produce it.
|
|
7
|
+
* In React, register the quantity field with `interactive: true` in `useCartForm`, which calls the function for you. Call the function yourself in Vue and in frameworks without a binding.
|
|
11
8
|
*
|
|
12
|
-
* @
|
|
9
|
+
* @param inputEl The quantity input that submits the form on change.
|
|
10
|
+
* @param formEl The cart form that holds the input and the set button.
|
|
11
|
+
* @returns A function that stops the automatic submits. Call the function when you remove the input.
|
|
12
|
+
* @throws If the form's first submit button isn't the set button, which has `name="intent"` and `value="set"`.
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* ```ts
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* detach();
|
|
25
25
|
* }
|
|
26
26
|
* ```
|
|
27
|
+
* @publicDocs
|
|
27
28
|
*/
|
|
28
29
|
declare function attachQuantityInput(inputEl: HTMLInputElement, formEl: HTMLFormElement): () => void;
|
|
29
30
|
//#endregion
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
//#region src/core/cart/attach-quantity-input.ts
|
|
2
2
|
const FIRST_SUBMIT_BUTTON_SELECTOR = "button:not([type=button])";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Submits a cart form when the customer changes the form's quantity input.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* first submit button (which must carry `name="intent" value="set"`). Returns
|
|
8
|
-
* a cleanup function that removes the event listener.
|
|
6
|
+
* Make the form's first submit button the set button from `register("set")`. The function throws when the first submit button is any other button.
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
* `register("set")` from `createCartFormRegister` to produce it.
|
|
8
|
+
* In React, register the quantity field with `interactive: true` in `useCartForm`, which calls the function for you. Call the function yourself in Vue and in frameworks without a binding.
|
|
12
9
|
*
|
|
13
|
-
* @
|
|
10
|
+
* @param inputEl The quantity input that submits the form on change.
|
|
11
|
+
* @param formEl The cart form that holds the input and the set button.
|
|
12
|
+
* @returns A function that stops the automatic submits. Call the function when you remove the input.
|
|
13
|
+
* @throws If the form's first submit button isn't the set button, which has `name="intent"` and `value="set"`.
|
|
14
14
|
*
|
|
15
15
|
* @example
|
|
16
16
|
* ```ts
|
|
@@ -25,6 +25,7 @@ const FIRST_SUBMIT_BUTTON_SELECTOR = "button:not([type=button])";
|
|
|
25
25
|
* detach();
|
|
26
26
|
* }
|
|
27
27
|
* ```
|
|
28
|
+
* @publicDocs
|
|
28
29
|
*/
|
|
29
30
|
function attachQuantityInput(inputEl, formEl) {
|
|
30
31
|
const firstSubmitButton = formEl.querySelector(FIRST_SUBMIT_BUTTON_SELECTOR);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attach-quantity-input.mjs","names":[],"sources":["../../../../src/core/cart/attach-quantity-input.ts"],"sourcesContent":["const FIRST_SUBMIT_BUTTON_SELECTOR = \"button:not([type=button])\";\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"attach-quantity-input.mjs","names":[],"sources":["../../../../src/core/cart/attach-quantity-input.ts"],"sourcesContent":["const FIRST_SUBMIT_BUTTON_SELECTOR = \"button:not([type=button])\";\n\n/**\n * Submits a cart form when the customer changes the form's quantity input.\n *\n * Make the form's first submit button the set button from `register(\"set\")`. The function throws when the first submit button is any other button.\n *\n * In React, register the quantity field with `interactive: true` in `useCartForm`, which calls the function for you. Call the function yourself in Vue and in frameworks without a binding.\n *\n * @param inputEl The quantity input that submits the form on change.\n * @param formEl The cart form that holds the input and the set button.\n * @returns A function that stops the automatic submits. Call the function when you remove the input.\n * @throws If the form's first submit button isn't the set button, which has `name=\"intent\"` and `value=\"set\"`.\n *\n * @example\n * ```ts\n * const input = document.querySelector<HTMLInputElement>('input[name=\"quantity\"]');\n * const form = input?.closest('form');\n *\n * if (input && form) {\n * const detach = attachQuantityInput(input, form);\n * // Input changes now auto-submit the form\n *\n * // Clean up when unmounting\n * detach();\n * }\n * ```\n * @publicDocs\n */\nexport function attachQuantityInput(\n inputEl: HTMLInputElement,\n formEl: HTMLFormElement,\n): () => void {\n const firstSubmitButton = formEl.querySelector(\n FIRST_SUBMIT_BUTTON_SELECTOR,\n ) as HTMLButtonElement | null;\n\n if (\n !firstSubmitButton ||\n firstSubmitButton.name !== \"intent\" ||\n firstSubmitButton.value !== \"set\"\n ) {\n throw new Error(\n `The first button in cart form must have a name of \"intent\" and value of \"set\". Your UI will not behave as expected.`,\n { cause: formEl },\n );\n }\n\n const handleChange = (e: Event) => {\n if (formEl.isConnected && e.target === inputEl) {\n formEl.requestSubmit(firstSubmitButton);\n }\n };\n\n inputEl.addEventListener(\"change\", handleChange);\n\n return () => {\n inputEl.removeEventListener(\"change\", handleChange);\n };\n}\n"],"mappings":";AAAA,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BrC,SAAgB,oBACd,SACA,QACY;CACZ,MAAM,oBAAoB,OAAO,cAC/B,6BACD;AAED,KACE,CAAC,qBACD,kBAAkB,SAAS,YAC3B,kBAAkB,UAAU,MAE5B,OAAM,IAAI,MACR,uHACA,EAAE,OAAO,QAAQ,CAClB;CAGH,MAAM,gBAAgB,MAAa;AACjC,MAAI,OAAO,eAAe,EAAE,WAAW,QACrC,QAAO,cAAc,kBAAkB;;AAI3C,SAAQ,iBAAiB,UAAU,aAAa;AAEhD,cAAa;AACX,UAAQ,oBAAoB,UAAU,aAAa"}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { StorefrontClient } from "../../client/types.mjs";
|
|
2
2
|
//#region src/core/cart/buyer-identity-sync.d.ts
|
|
3
3
|
declare const cartBuyerIdentitySync: unique symbol;
|
|
4
|
+
/** The request with the `cart` cookie and the Storefront API client that updates the cart. */
|
|
4
5
|
type CartBuyerIdentitySyncContext = {
|
|
5
|
-
request: Request;
|
|
6
|
+
/** The incoming request. The `cart` cookie on the request identifies the cart. */request: Request; /** The Storefront API client that updates the cart's buyer identity. */
|
|
6
7
|
storefrontClient: StorefrontClient;
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Connects the cart to the customer when the customer logs in, and disconnects the cart when the customer logs out.
|
|
11
|
+
*
|
|
12
|
+
* Create the cart server handlers with `customerSession`, then pass the handlers to `createCustomerAccountServerHandlers` through the `cartServerHandlers` option. The customer account handlers then update the cart for you.
|
|
12
13
|
*/
|
|
13
14
|
type CartBuyerIdentitySync = {
|
|
14
|
-
/** Attaches
|
|
15
|
+
/** Attaches the customer to the cart in the request's `cart` cookie, or detaches the customer when the token is `null`. */updateBuyerIdentity(context: CartBuyerIdentitySyncContext, customerAccessToken: string | null): Promise<void>; /** A `Set-Cookie` value that clears the `cart` cookie. The customer account handlers send the value when they can't detach the customer from the cart. */
|
|
15
16
|
readonly expiredCartCookie: string;
|
|
16
17
|
};
|
|
17
18
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buyer-identity-sync.mjs","names":[],"sources":["../../../../src/core/cart/buyer-identity-sync.ts"],"sourcesContent":["import type { StorefrontClient } from \"../../client\";\n\n// Deliberately a tiny standalone module: customer-account handlers import it at\n// runtime, so it must not pull the cart handler graph into their bundle.\nexport const cartBuyerIdentitySync: unique symbol = Symbol(\"hydrogen.cartBuyerIdentitySync\");\n\nexport type CartBuyerIdentitySyncContext = {\n request: Request;\n storefrontClient: StorefrontClient;\n};\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"buyer-identity-sync.mjs","names":[],"sources":["../../../../src/core/cart/buyer-identity-sync.ts"],"sourcesContent":["import type { StorefrontClient } from \"../../client\";\n\n// Deliberately a tiny standalone module: customer-account handlers import it at\n// runtime, so it must not pull the cart handler graph into their bundle.\nexport const cartBuyerIdentitySync: unique symbol = Symbol(\"hydrogen.cartBuyerIdentitySync\");\n\n/** The request with the `cart` cookie and the Storefront API client that updates the cart. */\nexport type CartBuyerIdentitySyncContext = {\n /** The incoming request. The `cart` cookie on the request identifies the cart. */\n request: Request;\n /** The Storefront API client that updates the cart's buyer identity. */\n storefrontClient: StorefrontClient;\n};\n\n/**\n * Connects the cart to the customer when the customer logs in, and disconnects the cart when the customer logs out.\n *\n * Create the cart server handlers with `customerSession`, then pass the handlers to `createCustomerAccountServerHandlers` through the `cartServerHandlers` option. The customer account handlers then update the cart for you.\n */\nexport type CartBuyerIdentitySync = {\n /** Attaches the customer to the cart in the request's `cart` cookie, or detaches the customer when the token is `null`. */\n updateBuyerIdentity(\n context: CartBuyerIdentitySyncContext,\n customerAccessToken: string | null,\n ): Promise<void>;\n /** A `Set-Cookie` value that clears the `cart` cookie. The customer account handlers send the value when they can't detach the customer from the cart. */\n readonly expiredCartCookie: string;\n};\n\n/** Cart server handlers that you created with `customerSession`. */\nexport type CartBuyerIdentitySyncSource = {\n readonly [cartBuyerIdentitySync]: CartBuyerIdentitySync;\n};\n\n// Accepts a partial source because JavaScript callers can pass cart handlers\n// created without customerSession; the caller turns undefined into an error.\nexport function getCartBuyerIdentitySync(\n source: Partial<CartBuyerIdentitySyncSource>,\n): CartBuyerIdentitySync | undefined {\n return source[cartBuyerIdentitySync];\n}\n"],"mappings":";AAIA,MAAa,wBAAuC,OAAO,iCAAiC"}
|