@shopify/hydrogen 2026.10.0-preview.2 → 2026.10.0-preview.4
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/README.md +34 -3
- package/dist/cli/index.mjs +578 -85
- package/dist/client/client.mjs +2 -2
- package/dist/client/errors.d.mts +19 -19
- package/dist/client/errors.mjs +19 -16
- package/dist/client/errors.mjs.map +1 -1
- package/dist/client/types.d.mts +91 -10
- package/dist/core/analytics/cart-tracker.d.mts +14 -0
- package/dist/core/analytics/cart-tracker.mjs +15 -1
- package/dist/core/analytics/cart-tracker.mjs.map +1 -1
- package/dist/core/analytics/custom-consent.mjs +12 -0
- package/dist/core/analytics/custom-consent.mjs.map +1 -0
- package/dist/core/analytics/events.d.mts +7 -0
- package/dist/core/analytics/events.mjs +7 -0
- package/dist/core/analytics/events.mjs.map +1 -1
- package/dist/core/analytics/index.d.mts +1 -1
- package/dist/core/analytics/types.d.mts +152 -16
- package/dist/core/cart/actions.d.mts +47 -3
- package/dist/core/cart/actions.mjs +29 -0
- package/dist/core/cart/actions.mjs.map +1 -1
- package/dist/core/cart/attach-quantity-input.d.mts +26 -0
- package/dist/core/cart/attach-quantity-input.mjs +26 -0
- package/dist/core/cart/attach-quantity-input.mjs.map +1 -1
- package/dist/core/cart/cart.d.mts +106 -6
- package/dist/core/cart/cart.mjs +57 -6
- package/dist/core/cart/cart.mjs.map +1 -1
- package/dist/core/cart/cookie.d.mts +15 -0
- package/dist/core/cart/cookie.mjs +15 -0
- package/dist/core/cart/cookie.mjs.map +1 -1
- package/dist/core/cart/form.d.mts +61 -0
- package/dist/core/cart/form.mjs +18 -0
- package/dist/core/cart/form.mjs.map +1 -1
- package/dist/core/cart/get-cart.d.mts +43 -2
- package/dist/core/cart/get-cart.mjs +33 -0
- package/dist/core/cart/get-cart.mjs.map +1 -1
- package/dist/core/cart/index.d.mts +1 -1
- package/dist/core/cart/quantity.d.mts +17 -0
- package/dist/core/cart/quantity.mjs +17 -0
- package/dist/core/cart/quantity.mjs.map +1 -1
- package/dist/core/cart/queries.d.mts +402 -369
- package/dist/core/cart/queries.mjs +2 -1
- package/dist/core/cart/queries.mjs.map +1 -1
- package/dist/core/cart/server-handlers.d.mts +69 -0
- package/dist/core/cart/server-handlers.mjs +2 -10
- package/dist/core/cart/server-handlers.mjs.map +1 -1
- package/dist/core/cart/state.d.mts +133 -0
- package/dist/core/cart/state.mjs +8 -0
- package/dist/core/cart/state.mjs.map +1 -1
- package/dist/core/collection/collection.d.mts +18 -2
- package/dist/core/collection/collection.mjs +11 -0
- package/dist/core/collection/collection.mjs.map +1 -1
- package/dist/core/collection/index.d.mts +1 -1
- package/dist/core/collection/reconciler.d.mts +13 -4
- package/dist/core/collection/reconciler.mjs.map +1 -1
- package/dist/core/collection/state.d.mts +7 -2
- package/dist/core/collection/state.mjs.map +1 -1
- package/dist/core/collection/url.d.mts +3 -0
- package/dist/core/collection/url.mjs.map +1 -1
- package/dist/core/constants.d.mts +1 -1
- package/dist/core/constants.mjs +2 -2
- package/dist/core/constants.mjs.map +1 -1
- package/dist/core/headers.mjs +2 -2
- package/dist/core/headers.mjs.map +1 -1
- package/dist/core/index.d.mts +5 -5
- package/dist/core/logging/logging.mjs.map +1 -1
- package/dist/core/money/format.mjs +6 -3
- package/dist/core/money/format.mjs.map +1 -1
- package/dist/core/observable.mjs +0 -3
- package/dist/core/observable.mjs.map +1 -1
- package/dist/core/predictive-search/form.d.mts +1 -0
- package/dist/core/predictive-search/form.mjs +1 -0
- package/dist/core/predictive-search/form.mjs.map +1 -1
- package/dist/core/predictive-search/index.d.mts +1 -1
- package/dist/core/predictive-search/queries.d.mts +40 -4
- package/dist/core/predictive-search/queries.mjs +7 -1
- package/dist/core/predictive-search/queries.mjs.map +1 -1
- package/dist/core/predictive-search/search.d.mts +43 -10
- package/dist/core/predictive-search/search.mjs +34 -0
- package/dist/core/predictive-search/search.mjs.map +1 -1
- package/dist/core/predictive-search/server-handlers.d.mts +31 -5
- package/dist/core/predictive-search/server-handlers.mjs +5 -3
- package/dist/core/predictive-search/server-handlers.mjs.map +1 -1
- package/dist/core/predictive-search/store.d.mts +3 -1
- package/dist/core/predictive-search/store.mjs.map +1 -1
- package/dist/core/predictive-search/url.d.mts +50 -8
- package/dist/core/predictive-search/url.mjs +9 -0
- package/dist/core/predictive-search/url.mjs.map +1 -1
- package/dist/core/product/form.d.mts +21 -2
- package/dist/core/product/form.mjs +13 -0
- package/dist/core/product/form.mjs.map +1 -1
- package/dist/core/product/index.d.mts +1 -1
- package/dist/core/product/options.d.mts +2 -2
- package/dist/core/product/options.mjs +2 -2
- package/dist/core/product/options.mjs.map +1 -1
- package/dist/core/product/product-form.d.mts +33 -1
- package/dist/core/product/product-form.mjs +30 -0
- package/dist/core/product/product-form.mjs.map +1 -1
- package/dist/core/product/state.d.mts +41 -0
- package/dist/core/product/url.d.mts +6 -0
- package/dist/core/product/url.mjs.map +1 -1
- package/dist/core/request-context.d.mts +8 -5
- package/dist/core/request-context.mjs +17 -39
- package/dist/core/request-context.mjs.map +1 -1
- package/dist/core/request-routing/handle-shopify-routes.mjs +4 -1
- package/dist/core/request-routing/handle-shopify-routes.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/ajax-api.mjs +1 -1
- package/dist/core/request-routing/interceptors/api-proxy.mjs +1 -1
- package/dist/core/request-routing/interceptors/checkout.mjs +22 -6
- package/dist/core/request-routing/interceptors/checkout.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
- package/dist/core/request-routing/interceptors/proxy.mjs +36 -10
- package/dist/core/request-routing/interceptors/proxy.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
- package/dist/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
- package/dist/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
- package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/well-known.mjs +1 -1
- package/dist/core/shopify-scripts/analytics.mjs +2 -2
- package/dist/core/shopify-scripts/analytics.mjs.map +1 -1
- package/dist/core/shopify-scripts/constants.mjs +4 -2
- package/dist/core/shopify-scripts/constants.mjs.map +1 -1
- package/dist/core/shopify-scripts/global.mjs +9 -4
- package/dist/core/shopify-scripts/global.mjs.map +1 -1
- package/dist/core/shopify-scripts/index.d.mts +1 -0
- package/dist/core/shopify-scripts/index.mjs +14 -12
- package/dist/core/shopify-scripts/index.mjs.map +1 -1
- package/dist/core/shopify-scripts/initialize.d.mts +1 -0
- package/dist/core/shopify-scripts/initialize.mjs +10 -3
- package/dist/core/shopify-scripts/initialize.mjs.map +1 -1
- package/dist/core/shopify-scripts/types.d.mts +6 -0
- package/dist/core/url.mjs +21 -2
- package/dist/core/url.mjs.map +1 -1
- package/dist/customer-account/client.d.mts +128 -4
- package/dist/customer-account/client.mjs +27 -1
- package/dist/customer-account/client.mjs.map +1 -1
- package/dist/customer-account/errors.d.mts +46 -0
- package/dist/customer-account/errors.mjs +46 -0
- package/dist/customer-account/errors.mjs.map +1 -1
- package/dist/customer-account/graphql.d.mts +68 -0
- package/dist/customer-account/graphql.mjs +47 -0
- package/dist/customer-account/graphql.mjs.map +1 -1
- package/dist/customer-account/session.d.mts +114 -23
- package/dist/customer-account/session.mjs +27 -12
- package/dist/customer-account/session.mjs.map +1 -1
- package/dist/customer-account/type-resolver.d.mts +23 -0
- package/dist/customer-account-api-types.d.ts +570 -374
- package/dist/customer-account.schema.json +2981 -1527
- package/dist/development/client/client.mjs +2 -2
- package/dist/development/client/errors.d.mts +19 -19
- package/dist/development/client/errors.mjs +20 -20
- package/dist/development/client/errors.mjs.map +1 -1
- package/dist/development/client/types.d.mts +91 -10
- package/dist/development/core/analytics/cart-tracker.d.mts +14 -0
- package/dist/development/core/analytics/cart-tracker.mjs +15 -1
- package/dist/development/core/analytics/cart-tracker.mjs.map +1 -1
- package/dist/development/core/analytics/custom-consent.mjs +12 -0
- package/dist/development/core/analytics/custom-consent.mjs.map +1 -0
- package/dist/development/core/analytics/events.d.mts +7 -0
- package/dist/development/core/analytics/events.mjs +7 -0
- package/dist/development/core/analytics/events.mjs.map +1 -1
- package/dist/development/core/analytics/index.d.mts +1 -1
- package/dist/development/core/analytics/types.d.mts +152 -16
- package/dist/development/core/cart/actions.d.mts +47 -3
- package/dist/development/core/cart/actions.mjs +29 -0
- package/dist/development/core/cart/actions.mjs.map +1 -1
- package/dist/development/core/cart/attach-quantity-input.d.mts +26 -0
- package/dist/development/core/cart/attach-quantity-input.mjs +26 -0
- package/dist/development/core/cart/attach-quantity-input.mjs.map +1 -1
- package/dist/development/core/cart/cart.d.mts +106 -6
- package/dist/development/core/cart/cart.mjs +57 -6
- package/dist/development/core/cart/cart.mjs.map +1 -1
- package/dist/development/core/cart/cookie.d.mts +15 -0
- package/dist/development/core/cart/cookie.mjs +15 -0
- package/dist/development/core/cart/cookie.mjs.map +1 -1
- package/dist/development/core/cart/form.d.mts +61 -0
- package/dist/development/core/cart/form.mjs +18 -0
- package/dist/development/core/cart/form.mjs.map +1 -1
- package/dist/development/core/cart/get-cart.d.mts +43 -2
- package/dist/development/core/cart/get-cart.mjs +33 -0
- package/dist/development/core/cart/get-cart.mjs.map +1 -1
- package/dist/development/core/cart/index.d.mts +1 -1
- package/dist/development/core/cart/quantity.d.mts +17 -0
- package/dist/development/core/cart/quantity.mjs +17 -0
- package/dist/development/core/cart/quantity.mjs.map +1 -1
- package/dist/development/core/cart/queries.d.mts +345 -312
- package/dist/development/core/cart/queries.mjs +2 -1
- package/dist/development/core/cart/queries.mjs.map +1 -1
- package/dist/development/core/cart/server-handlers.d.mts +69 -0
- package/dist/development/core/cart/server-handlers.mjs +2 -10
- package/dist/development/core/cart/server-handlers.mjs.map +1 -1
- package/dist/development/core/cart/state.d.mts +133 -0
- package/dist/development/core/cart/state.mjs +8 -0
- package/dist/development/core/cart/state.mjs.map +1 -1
- package/dist/development/core/collection/collection.d.mts +18 -2
- package/dist/development/core/collection/collection.mjs +11 -0
- package/dist/development/core/collection/collection.mjs.map +1 -1
- package/dist/development/core/collection/index.d.mts +1 -1
- package/dist/development/core/collection/reconciler.d.mts +13 -4
- package/dist/development/core/collection/reconciler.mjs.map +1 -1
- package/dist/development/core/collection/state.d.mts +7 -2
- package/dist/development/core/collection/state.mjs.map +1 -1
- package/dist/development/core/collection/url.d.mts +3 -0
- package/dist/development/core/collection/url.mjs.map +1 -1
- package/dist/development/core/constants.mjs +1 -1
- package/dist/development/core/constants.mjs.map +1 -1
- package/dist/development/core/development.d.mts +5 -5
- package/dist/development/core/headers.mjs +2 -2
- package/dist/development/core/headers.mjs.map +1 -1
- package/dist/development/core/index.d.mts +4 -4
- package/dist/development/core/logging/logging.mjs.map +1 -1
- package/dist/development/core/money/format.mjs +6 -3
- package/dist/development/core/money/format.mjs.map +1 -1
- package/dist/development/core/observable.mjs +0 -3
- package/dist/development/core/observable.mjs.map +1 -1
- package/dist/development/core/predictive-search/form.d.mts +1 -0
- package/dist/development/core/predictive-search/form.mjs +1 -0
- package/dist/development/core/predictive-search/form.mjs.map +1 -1
- package/dist/development/core/predictive-search/index.d.mts +1 -1
- package/dist/development/core/predictive-search/queries.d.mts +38 -2
- package/dist/development/core/predictive-search/queries.mjs +7 -1
- package/dist/development/core/predictive-search/queries.mjs.map +1 -1
- package/dist/development/core/predictive-search/search.d.mts +43 -10
- package/dist/development/core/predictive-search/search.mjs +34 -0
- package/dist/development/core/predictive-search/search.mjs.map +1 -1
- package/dist/development/core/predictive-search/server-handlers.d.mts +31 -5
- package/dist/development/core/predictive-search/server-handlers.mjs +5 -3
- package/dist/development/core/predictive-search/server-handlers.mjs.map +1 -1
- package/dist/development/core/predictive-search/store.d.mts +3 -1
- package/dist/development/core/predictive-search/store.mjs.map +1 -1
- package/dist/development/core/predictive-search/url.d.mts +50 -8
- package/dist/development/core/predictive-search/url.mjs +9 -0
- package/dist/development/core/predictive-search/url.mjs.map +1 -1
- package/dist/development/core/product/form.d.mts +21 -2
- package/dist/development/core/product/form.mjs +13 -0
- package/dist/development/core/product/form.mjs.map +1 -1
- package/dist/development/core/product/index.d.mts +1 -1
- package/dist/development/core/product/options.d.mts +2 -2
- package/dist/development/core/product/options.mjs +2 -2
- package/dist/development/core/product/options.mjs.map +1 -1
- package/dist/development/core/product/product-form.d.mts +33 -1
- package/dist/development/core/product/product-form.mjs +30 -0
- package/dist/development/core/product/product-form.mjs.map +1 -1
- package/dist/development/core/product/state.d.mts +41 -0
- package/dist/development/core/product/url.d.mts +6 -0
- package/dist/development/core/product/url.mjs.map +1 -1
- package/dist/development/core/request-context.d.mts +8 -5
- package/dist/development/core/request-context.mjs +17 -39
- package/dist/development/core/request-context.mjs.map +1 -1
- package/dist/development/core/request-routing/handle-shopify-routes.mjs +4 -1
- package/dist/development/core/request-routing/handle-shopify-routes.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/ajax-api.mjs +1 -1
- package/dist/development/core/request-routing/interceptors/api-proxy.mjs +1 -1
- package/dist/development/core/request-routing/interceptors/checkout.mjs +22 -6
- package/dist/development/core/request-routing/interceptors/checkout.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
- package/dist/development/core/request-routing/interceptors/proxy.mjs +36 -10
- package/dist/development/core/request-routing/interceptors/proxy.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
- package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
- package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
- package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/well-known.mjs +1 -1
- package/dist/development/core/shopify-scripts/analytics.mjs +2 -2
- package/dist/development/core/shopify-scripts/analytics.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/constants.mjs +4 -2
- package/dist/development/core/shopify-scripts/constants.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/global.mjs +9 -4
- package/dist/development/core/shopify-scripts/global.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/index.d.mts +1 -0
- package/dist/development/core/shopify-scripts/index.mjs +14 -12
- package/dist/development/core/shopify-scripts/index.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/initialize.d.mts +1 -0
- package/dist/development/core/shopify-scripts/initialize.mjs +10 -3
- package/dist/development/core/shopify-scripts/initialize.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/types.d.mts +6 -0
- package/dist/development/core/url.mjs +21 -2
- package/dist/development/core/url.mjs.map +1 -1
- package/dist/development/customer-account/session.d.mts +69 -13
- package/dist/development/customer-account/session.mjs +4 -0
- package/dist/development/customer-account/session.mjs.map +1 -1
- package/dist/development/globals.d.mts +6 -5
- package/dist/development/graphql/generated/graphql-env.d.mts +459 -16
- package/dist/development/graphql/generated/storefront-api-types.d.mts +1 -1
- package/dist/development/graphql/graphql.d.mts +33 -0
- package/dist/development/graphql/graphql.mjs +30 -0
- package/dist/development/graphql/graphql.mjs.map +1 -1
- package/dist/development/graphql/type-resolver.d.mts +14 -0
- package/dist/development/react/cart.d.mts +85 -14
- package/dist/development/react/cart.mjs +123 -3
- package/dist/development/react/cart.mjs.map +1 -1
- package/dist/development/react/collection.d.mts +2 -4
- package/dist/development/react/collection.mjs.map +1 -1
- package/dist/development/react/index.d.mts +8 -6
- package/dist/development/react/index.mjs +2 -2
- package/dist/development/react/predictive-search.d.mts +68 -7
- package/dist/development/react/predictive-search.mjs +54 -12
- package/dist/development/react/predictive-search.mjs.map +1 -1
- package/dist/development/react/product.d.mts +1 -0
- package/dist/development/react/product.mjs +5 -2
- package/dist/development/react/product.mjs.map +1 -1
- package/dist/development/react/shopify-scripts.mjs +1 -0
- package/dist/development/react/shopify-scripts.mjs.map +1 -1
- package/dist/development/vue/cart.d.mts +85 -6
- package/dist/development/vue/cart.mjs +95 -0
- package/dist/development/vue/cart.mjs.map +1 -1
- package/dist/development/vue/collection.d.mts +83 -5
- package/dist/development/vue/collection.mjs +52 -0
- package/dist/development/vue/collection.mjs.map +1 -1
- package/dist/development/vue/index.d.mts +6 -4
- package/dist/development/vue/predictive-search.d.mts +53 -6
- package/dist/development/vue/predictive-search.mjs +33 -0
- package/dist/development/vue/predictive-search.mjs.map +1 -1
- package/dist/development/vue/product.d.mts +6 -2
- package/dist/development/vue/product.mjs.map +1 -1
- package/dist/development/vue/shopify-scripts.d.mts +30 -3
- package/dist/development/vue/shopify-scripts.mjs +6 -0
- package/dist/development/vue/shopify-scripts.mjs.map +1 -1
- package/dist/globals.d.mts +6 -5
- package/dist/graphql/generated/customer-account-graphql-env.d.mts +1717 -1090
- package/dist/graphql/generated/graphql-env.d.mts +459 -16
- package/dist/graphql/generated/storefront-api-types.d.mts +1 -1
- package/dist/graphql/graphql.d.mts +33 -0
- package/dist/graphql/graphql.mjs +30 -0
- package/dist/graphql/graphql.mjs.map +1 -1
- package/dist/graphql/type-resolver.d.mts +14 -0
- package/dist/react/cart.d.mts +85 -14
- package/dist/react/cart.mjs +123 -3
- package/dist/react/cart.mjs.map +1 -1
- package/dist/react/collection.d.mts +2 -4
- package/dist/react/collection.mjs.map +1 -1
- package/dist/react/index.d.mts +8 -6
- package/dist/react/index.mjs +2 -2
- package/dist/react/predictive-search.d.mts +68 -7
- package/dist/react/predictive-search.mjs +54 -12
- package/dist/react/predictive-search.mjs.map +1 -1
- package/dist/react/product.d.mts +1 -0
- package/dist/react/product.mjs +5 -2
- package/dist/react/product.mjs.map +1 -1
- package/dist/react/shopify-scripts.mjs +1 -0
- package/dist/react/shopify-scripts.mjs.map +1 -1
- package/dist/storefront-api-types.d.ts +165 -20
- package/dist/storefront.schema.json +782 -57
- package/dist/vite/customer-account.mjs +191 -0
- package/dist/vite/customer-account.mjs.map +1 -0
- package/dist/vite/local-https.mjs +13 -29
- package/dist/vite/local-https.mjs.map +1 -1
- package/dist/vite/mkcert.mjs +1 -1
- package/dist/vue/cart.d.mts +85 -6
- package/dist/vue/cart.mjs +95 -0
- package/dist/vue/cart.mjs.map +1 -1
- package/dist/vue/collection.d.mts +83 -5
- package/dist/vue/collection.mjs +52 -0
- package/dist/vue/collection.mjs.map +1 -1
- package/dist/vue/index.d.mts +6 -4
- package/dist/vue/predictive-search.d.mts +53 -6
- package/dist/vue/predictive-search.mjs +33 -0
- package/dist/vue/predictive-search.mjs.map +1 -1
- package/dist/vue/product.d.mts +6 -2
- package/dist/vue/product.mjs.map +1 -1
- package/dist/vue/shopify-scripts.d.mts +31 -4
- package/dist/vue/shopify-scripts.mjs +6 -0
- package/dist/vue/shopify-scripts.mjs.map +1 -1
- package/package.json +5 -2
- package/skills/hydrogen-analytics/SKILL.md +6 -5
- package/skills/hydrogen-analytics/references/custom-consent.md +52 -0
- package/skills/hydrogen-analytics/references/react.md +1 -1
- package/skills/hydrogen-cart-metafields/SKILL.md +133 -0
- package/skills/hydrogen-cart-ui/SKILL.md +2 -1
- package/skills/hydrogen-customer-account/SKILL.md +3 -3
- package/skills/hydrogen-local-https/SKILL.md +22 -7
- package/skills/hydrogen-request-handlers/SKILL.md +5 -5
- package/skills/hydrogen-request-handlers/references/nuxt.md +0 -6
- package/skills/hydrogen-setup/references/analytics.md +24 -23
- package/skills/hydrogen-setup/steps/1-inspect-app.md +1 -1
- package/skills/hydrogen-setup/steps/11-verify.md +1 -0
- package/skills/hydrogen-setup/steps/2-scaffold.md +22 -1
- package/skills/hydrogen-smoke-test/SKILL.md +2 -2
- package/skills/hydrogen-storefront-client/SKILL.md +4 -2
- package/skills/hydrogen-variant-form/SKILL.md +10 -4
- package/ts-plugin/package.json +5 -0
- package/dist/core/shopify-scripts/consent.mjs +0 -17
- package/dist/core/shopify-scripts/consent.mjs.map +0 -1
- package/dist/core/shopify-scripts/deprecated-cookies.mjs +0 -97
- package/dist/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
- package/dist/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
- package/dist/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
- package/dist/core/shopify-scripts/utils/tracking-values.mjs +0 -65
- package/dist/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
- package/dist/core/shopify-scripts/utils/uuid.mjs +0 -17
- package/dist/core/shopify-scripts/utils/uuid.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/consent.mjs +0 -17
- package/dist/development/core/shopify-scripts/consent.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/deprecated-cookies.mjs +0 -97
- package/dist/development/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
- package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/utils/tracking-values.mjs +0 -65
- package/dist/development/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/utils/uuid.mjs +0 -17
- package/dist/development/core/shopify-scripts/utils/uuid.mjs.map +0 -1
|
@@ -55,7 +55,7 @@ declare const PREDICTIVE_SEARCH_QUERY: StorefrontQueryString<{
|
|
|
55
55
|
} | null;
|
|
56
56
|
price: {
|
|
57
57
|
amount: string;
|
|
58
|
-
currencyCode: "ALL" | "USD" | "EUR" | "GBP" | "CAD" | "AFN" | "DZD" | "AOA" | "ARS" | "AMD" | "AWG" | "AUD" | "BBD" | "AZN" | "BDT" | "BSD" | "BHD" | "BIF" | "BZD" | "BMD" | "BTN" | "BAM" | "BRL" | "BOB" | "BWP" | "BND" | "BGN" | "MMK" | "KHR" | "CVE" | "KYD" | "XAF" | "CLP" | "CNY" | "COP" | "KMF" | "CDF" | "CRC" | "HRK" | "CZK" | "DKK" | "DOP" | "XCD" | "EGP" | "ERN" | "ETB" | "FKP" | "XPF" | "FJD" | "GIP" | "GMD" | "GHS" | "GTQ" | "GYD" | "GEL" | "HTG" | "HNL" | "HKD" | "HUF" | "ISK" | "INR" | "IDR" | "ILS" | "IQD" | "JMD" | "JPY" | "JEP" | "JOD" | "KZT" | "KES" | "KID" | "KWD" | "KGS" | "LAK" | "LVL" | "LBP" | "LSL" | "LRD" | "LTL" | "MGA" | "MKD" | "MOP" | "MWK" | "MVR" | "MRU" | "MXN" | "MYR" | "MUR" | "MDL" | "MAD" | "MNT" | "MZN" | "NAD" | "NPR" | "ANG" | "NZD" | "NIO" | "NGN" | "NOK" | "OMR" | "PAB" | "PKR" | "PGK" | "PYG" | "PEN" | "PHP" | "PLN" | "QAR" | "RON" | "RUB" | "RWF" | "WST" | "SHP" | "SAR" | "RSD" | "SCR" | "SGD" | "SDG" | "SOS" | "SYP" | "ZAR" | "KRW" | "SSP" | "SBD" | "LKR" | "SRD" | "SZL" | "SEK" | "CHF" | "TWD" | "THB" | "TZS" | "TTD" | "TND" | "TRY" | "TMT" | "UGX" | "UAH" | "AED" | "UYU" | "UZS" | "VUV" | "VES" | "VND" | "XOF" | "YER" | "ZMW" | "BYN" | "BYR" | "DJF" | "GNF" | "IRR" | "LYD" | "SLL" | "STD" | "STN" | "TJS" | "TOP" | "VED" | "VEF" | "XXX";
|
|
58
|
+
currencyCode: "ALL" | "USD" | "EUR" | "GBP" | "CAD" | "AFN" | "DZD" | "AOA" | "ARS" | "AMD" | "AWG" | "AUD" | "BBD" | "AZN" | "BDT" | "BSD" | "BHD" | "BIF" | "BZD" | "BMD" | "BTN" | "BAM" | "BRL" | "BOB" | "BWP" | "BND" | "BGN" | "MMK" | "KHR" | "CVE" | "XCG" | "KYD" | "XAF" | "CLP" | "CNY" | "COP" | "KMF" | "CDF" | "CRC" | "HRK" | "CZK" | "DKK" | "DOP" | "XCD" | "EGP" | "ERN" | "ETB" | "FKP" | "XPF" | "FJD" | "GIP" | "GMD" | "GHS" | "GTQ" | "GYD" | "GEL" | "HTG" | "HNL" | "HKD" | "HUF" | "ISK" | "INR" | "IDR" | "ILS" | "IQD" | "JMD" | "JPY" | "JEP" | "JOD" | "KZT" | "KES" | "KID" | "KWD" | "KGS" | "LAK" | "LVL" | "LBP" | "LSL" | "LRD" | "LTL" | "MGA" | "MKD" | "MOP" | "MWK" | "MVR" | "MRU" | "MXN" | "MYR" | "MUR" | "MDL" | "MAD" | "MNT" | "MZN" | "NAD" | "NPR" | "ANG" | "NZD" | "NIO" | "NGN" | "NOK" | "OMR" | "PAB" | "PKR" | "PGK" | "PYG" | "PEN" | "PHP" | "PLN" | "QAR" | "RON" | "RUB" | "RWF" | "WST" | "SHP" | "SAR" | "RSD" | "SCR" | "SGD" | "SDG" | "SOS" | "SYP" | "ZAR" | "KRW" | "SSP" | "SBD" | "LKR" | "SRD" | "SZL" | "SEK" | "CHF" | "TWD" | "THB" | "TZS" | "TTD" | "TND" | "TRY" | "TMT" | "UGX" | "UAH" | "AED" | "UYU" | "UZS" | "VUV" | "VES" | "VND" | "XOF" | "YER" | "ZMW" | "BYN" | "BYR" | "DJF" | "GNF" | "IRR" | "LYD" | "SLL" | "STD" | "STN" | "TJS" | "TOP" | "VED" | "VEF" | "XXX";
|
|
59
59
|
};
|
|
60
60
|
} | null;
|
|
61
61
|
}[];
|
|
@@ -91,6 +91,21 @@ declare const DEFAULT_ARTICLE_FRAGMENT: StorefrontQueryString<{
|
|
|
91
91
|
declare const DEFAULT_QUERY_FRAGMENT: StorefrontQueryString<{
|
|
92
92
|
text: string;
|
|
93
93
|
}, never, "\n fragment PredictiveSearchQueryFragment on SearchQuerySuggestion {\n text\n }\n">;
|
|
94
|
+
/**
|
|
95
|
+
* Additional GraphQL fragments that select extra fields on each predictive search result type.
|
|
96
|
+
*
|
|
97
|
+
* Each fragment must be named and typed as follows, or
|
|
98
|
+
* `makePredictiveSearchQueries` throws:
|
|
99
|
+
*
|
|
100
|
+
* - `product`: `fragment PredictiveSearchProductFragment on Product`
|
|
101
|
+
* - `collection`: `fragment PredictiveSearchCollectionFragment on Collection`
|
|
102
|
+
* - `page`: `fragment PredictiveSearchPageFragment on Page`
|
|
103
|
+
* - `article`: `fragment PredictiveSearchArticleFragment on Article`
|
|
104
|
+
* - `query`: `fragment PredictiveSearchQueryFragment on SearchQuerySuggestion`
|
|
105
|
+
*
|
|
106
|
+
* Hydrogen's own fields (used for URL generation, tracking, and display) are
|
|
107
|
+
* always selected; custom fragments are merged alongside them.
|
|
108
|
+
*/
|
|
94
109
|
type PredictiveSearchFragments = {
|
|
95
110
|
readonly product?: AnyStorefrontQueryString;
|
|
96
111
|
readonly collection?: AnyStorefrontQueryString;
|
|
@@ -98,8 +113,9 @@ type PredictiveSearchFragments = {
|
|
|
98
113
|
readonly article?: AnyStorefrontQueryString;
|
|
99
114
|
readonly query?: AnyStorefrontQueryString;
|
|
100
115
|
};
|
|
116
|
+
/** Options for {@link makePredictiveSearchQueries}. */
|
|
101
117
|
type CreatePredictiveSearchQueriesOptions<TFragments extends PredictiveSearchFragments = PredictiveSearchFragments> = {
|
|
102
|
-
readonly fragments?: TFragments;
|
|
118
|
+
/** Extra fields to select per result type. Hydrogen's built-in fields are always included. */readonly fragments?: TFragments;
|
|
103
119
|
};
|
|
104
120
|
type FragmentForOptions<TOptions, TKey extends keyof PredictiveSearchFragments, TDefault extends AnyStorefrontQueryString> = TOptions extends {
|
|
105
121
|
readonly fragments: infer TFragments;
|
|
@@ -107,11 +123,31 @@ type FragmentForOptions<TOptions, TKey extends keyof PredictiveSearchFragments,
|
|
|
107
123
|
type PredictiveSearchQueryFragmentsForOptions<TOptions> = [FragmentForOptions<TOptions, "product", typeof DEFAULT_PRODUCT_FRAGMENT>, FragmentForOptions<TOptions, "collection", typeof DEFAULT_COLLECTION_FRAGMENT>, FragmentForOptions<TOptions, "page", typeof DEFAULT_PAGE_FRAGMENT>, FragmentForOptions<TOptions, "article", typeof DEFAULT_ARTICLE_FRAGMENT>, FragmentForOptions<TOptions, "query", typeof DEFAULT_QUERY_FRAGMENT>];
|
|
108
124
|
type PredictiveSearchQuerySourceForOptions<TOptions> = ComposedSource<SourceOf<typeof PREDICTIVE_SEARCH_QUERY>, PredictiveSearchQueryFragmentsForOptions<TOptions>>;
|
|
109
125
|
type PredictiveSearchQueryForOptions<TOptions> = StorefrontQueryString<InferResult<PredictiveSearchQuerySourceForOptions<TOptions>>, InferVariables<PredictiveSearchQuerySourceForOptions<TOptions>>, PredictiveSearchQuerySourceForOptions<TOptions>>;
|
|
126
|
+
/** Resolves the query map shape from {@link CreatePredictiveSearchQueriesOptions}, preserving full type inference from custom fragments. */
|
|
110
127
|
type PredictiveSearchQueriesForOptions<TOptions> = {
|
|
111
128
|
readonly predictiveSearch: PredictiveSearchQueryForOptions<TOptions>;
|
|
112
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* Composes the predictive search GraphQL query document from the built-in
|
|
132
|
+
* base query and either custom or default fragments.
|
|
133
|
+
*
|
|
134
|
+
* Custom fragments are validated at runtime against their expected names
|
|
135
|
+
* and target GraphQL types. See {@link PredictiveSearchFragments} for the
|
|
136
|
+
* naming contract.
|
|
137
|
+
*
|
|
138
|
+
* The returned object's `predictiveSearch` key carries the composed query
|
|
139
|
+
* with full type inference from the provided fragments.
|
|
140
|
+
*
|
|
141
|
+
* @throws {Error} When a custom fragment does not match its required name or target type.
|
|
142
|
+
*/
|
|
113
143
|
declare function makePredictiveSearchQueries<const TOptions extends CreatePredictiveSearchQueriesOptions>(options: TOptions): PredictiveSearchQueriesForOptions<TOptions>;
|
|
114
144
|
declare function makePredictiveSearchQueries(): PredictiveSearchQueriesForOptions<undefined>;
|
|
145
|
+
/**
|
|
146
|
+
* Internal default query object built from Hydrogen's default fragments;
|
|
147
|
+
* used when `query` is omitted.
|
|
148
|
+
*
|
|
149
|
+
* Build custom query documents with {@link makePredictiveSearchQueries}.
|
|
150
|
+
*/
|
|
115
151
|
declare const predictiveSearchQueries: PredictiveSearchQueriesForOptions<undefined>;
|
|
116
152
|
//#endregion
|
|
117
153
|
export { CreatePredictiveSearchQueriesOptions, PredictiveSearchFragments, PredictiveSearchQueriesForOptions, makePredictiveSearchQueries, predictiveSearchQueries };
|
|
@@ -54,7 +54,7 @@ const DEFAULT_PAGE_FRAGMENT = gql(`fragment PredictiveSearchPageFragment on Page
|
|
|
54
54
|
const DEFAULT_ARTICLE_FRAGMENT = gql(`fragment PredictiveSearchArticleFragment on Article{id}`);
|
|
55
55
|
const DEFAULT_QUERY_FRAGMENT = gql(`fragment PredictiveSearchQueryFragment on SearchQuerySuggestion{text}`);
|
|
56
56
|
function assertFragmentContract(fragment, contract) {
|
|
57
|
-
if (new RegExp(`fragment\\s+${contract.name}\\s+on\\s+${contract.typeName}`).test(fragment)) return;
|
|
57
|
+
if (new RegExp(`fragment\\s+${contract.name}\\s+on\\s+${contract.typeName}\\b`).test(fragment)) return;
|
|
58
58
|
throw new Error(`Predictive search ${contract.label} fragment must be named ${contract.name} and target ${contract.typeName}`);
|
|
59
59
|
}
|
|
60
60
|
function resolveFragments(fragments) {
|
|
@@ -74,6 +74,12 @@ function resolveFragments(fragments) {
|
|
|
74
74
|
function makePredictiveSearchQueries(options) {
|
|
75
75
|
return { predictiveSearch: gql(PREDICTIVE_SEARCH_QUERY, resolveFragments(options?.fragments)) };
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Internal default query object built from Hydrogen's default fragments;
|
|
79
|
+
* used when `query` is omitted.
|
|
80
|
+
*
|
|
81
|
+
* Build custom query documents with {@link makePredictiveSearchQueries}.
|
|
82
|
+
*/
|
|
77
83
|
const predictiveSearchQueries = makePredictiveSearchQueries();
|
|
78
84
|
|
|
79
85
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.mjs","names":[],"sources":["../../../../src/core/predictive-search/queries.ts"],"sourcesContent":["import {\n gql,\n type AnyStorefrontQueryString,\n type ComposedSource,\n type SourceOf,\n type StorefrontQueryString,\n} from \"../../graphql\";\nimport type { InferResult, InferVariables } from \"../../graphql\";\n\nconst PRODUCT_FRAGMENT_NAME = \"PredictiveSearchProductFragment\";\nconst COLLECTION_FRAGMENT_NAME = \"PredictiveSearchCollectionFragment\";\nconst PAGE_FRAGMENT_NAME = \"PredictiveSearchPageFragment\";\nconst ARTICLE_FRAGMENT_NAME = \"PredictiveSearchArticleFragment\";\nconst QUERY_FRAGMENT_NAME = \"PredictiveSearchQueryFragment\";\n\nconst PRODUCT_TYPE_NAME = \"Product\";\nconst COLLECTION_TYPE_NAME = \"Collection\";\nconst PAGE_TYPE_NAME = \"Page\";\nconst ARTICLE_TYPE_NAME = \"Article\";\nconst QUERY_TYPE_NAME = \"SearchQuerySuggestion\";\n\ntype FragmentContract = {\n readonly label: string;\n readonly name: string;\n readonly typeName: string;\n};\n\nconst PRODUCT_CONTRACT = {\n label: \"product\",\n name: PRODUCT_FRAGMENT_NAME,\n typeName: PRODUCT_TYPE_NAME,\n} as const satisfies FragmentContract;\n\nconst COLLECTION_CONTRACT = {\n label: \"collection\",\n name: COLLECTION_FRAGMENT_NAME,\n typeName: COLLECTION_TYPE_NAME,\n} as const satisfies FragmentContract;\n\nconst PAGE_CONTRACT = {\n label: \"page\",\n name: PAGE_FRAGMENT_NAME,\n typeName: PAGE_TYPE_NAME,\n} as const satisfies FragmentContract;\n\nconst ARTICLE_CONTRACT = {\n label: \"article\",\n name: ARTICLE_FRAGMENT_NAME,\n typeName: ARTICLE_TYPE_NAME,\n} as const satisfies FragmentContract;\n\nconst QUERY_CONTRACT = {\n label: \"query\",\n name: QUERY_FRAGMENT_NAME,\n typeName: QUERY_TYPE_NAME,\n} as const satisfies FragmentContract;\n\nconst HYDROGEN_PRODUCT_FRAGMENT = gql(`\n fragment HydrogenPredictiveSearchProductFragment on Product {\n __typename\n id\n title\n handle\n trackingParameters\n selectedOrFirstAvailableVariant(\n selectedOptions: []\n ignoreUnknownOptions: true\n caseInsensitiveMatch: true\n ) {\n id\n image {\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n }\n }\n`);\n\nconst HYDROGEN_COLLECTION_FRAGMENT = gql(`\n fragment HydrogenPredictiveSearchCollectionFragment on Collection {\n __typename\n id\n title\n handle\n image {\n url\n altText\n width\n height\n }\n trackingParameters\n }\n`);\n\nconst HYDROGEN_PAGE_FRAGMENT = gql(`\n fragment HydrogenPredictiveSearchPageFragment on Page {\n __typename\n id\n title\n handle\n trackingParameters\n }\n`);\n\nconst HYDROGEN_ARTICLE_FRAGMENT = gql(`\n fragment HydrogenPredictiveSearchArticleFragment on Article {\n __typename\n id\n title\n handle\n blog {\n handle\n }\n image {\n url\n altText\n width\n height\n }\n trackingParameters\n }\n`);\n\nconst HYDROGEN_QUERY_FRAGMENT = gql(`\n fragment HydrogenPredictiveSearchQueryFragment on SearchQuerySuggestion {\n __typename\n text\n styledText\n trackingParameters\n }\n`);\n\n// The consumer-overridable fragment spreads (e.g. PredictiveSearchProductFragment)\n// are interpolated on purpose: those fragments only exist at runtime, and the\n// gql.tada plugin skips documents containing interpolations instead of flagging\n// unknown fragments, while TypeScript still resolves the full literal source type.\n// The resolved fragments are composed in at runtime by `makePredictiveSearchQueries`.\nconst PREDICTIVE_SEARCH_QUERY = gql(\n `query PredictiveSearch(\n $country: CountryCode\n $language: LanguageCode\n $limit: Int\n $limitScope: PredictiveSearchLimitScope\n $term: String!\n $types: [PredictiveSearchType!]\n $searchableFields: [SearchableField!]\n $unavailableProducts: SearchUnavailableProductsType\n ) @inContext(country: $country, language: $language) {\n predictiveSearch(\n limit: $limit\n limitScope: $limitScope\n query: $term\n types: $types\n searchableFields: $searchableFields\n unavailableProducts: $unavailableProducts\n ) {\n articles {\n ...HydrogenPredictiveSearchArticleFragment\n ...${ARTICLE_FRAGMENT_NAME}\n }\n collections {\n ...HydrogenPredictiveSearchCollectionFragment\n ...${COLLECTION_FRAGMENT_NAME}\n }\n pages {\n ...HydrogenPredictiveSearchPageFragment\n ...${PAGE_FRAGMENT_NAME}\n }\n products {\n ...HydrogenPredictiveSearchProductFragment\n ...${PRODUCT_FRAGMENT_NAME}\n }\n queries {\n ...HydrogenPredictiveSearchQueryFragment\n ...${QUERY_FRAGMENT_NAME}\n }\n }\n }`,\n [\n HYDROGEN_PRODUCT_FRAGMENT,\n HYDROGEN_COLLECTION_FRAGMENT,\n HYDROGEN_PAGE_FRAGMENT,\n HYDROGEN_ARTICLE_FRAGMENT,\n HYDROGEN_QUERY_FRAGMENT,\n ],\n);\n\nconst DEFAULT_PRODUCT_FRAGMENT = gql(`\n fragment PredictiveSearchProductFragment on Product {\n id\n }\n`);\n\nconst DEFAULT_COLLECTION_FRAGMENT = gql(`\n fragment PredictiveSearchCollectionFragment on Collection {\n id\n }\n`);\n\nconst DEFAULT_PAGE_FRAGMENT = gql(`\n fragment PredictiveSearchPageFragment on Page {\n id\n }\n`);\n\nconst DEFAULT_ARTICLE_FRAGMENT = gql(`\n fragment PredictiveSearchArticleFragment on Article {\n id\n }\n`);\n\nconst DEFAULT_QUERY_FRAGMENT = gql(`\n fragment PredictiveSearchQueryFragment on SearchQuerySuggestion {\n text\n }\n`);\n\nexport type PredictiveSearchFragments = {\n readonly product?: AnyStorefrontQueryString;\n readonly collection?: AnyStorefrontQueryString;\n readonly page?: AnyStorefrontQueryString;\n readonly article?: AnyStorefrontQueryString;\n readonly query?: AnyStorefrontQueryString;\n};\n\nexport type CreatePredictiveSearchQueriesOptions<\n TFragments extends PredictiveSearchFragments = PredictiveSearchFragments,\n> = {\n readonly fragments?: TFragments;\n};\n\ntype FragmentForOptions<\n TOptions,\n TKey extends keyof PredictiveSearchFragments,\n TDefault extends AnyStorefrontQueryString,\n> = TOptions extends { readonly fragments: infer TFragments }\n ? TFragments extends Record<TKey, infer TFragment extends AnyStorefrontQueryString>\n ? TFragment\n : TDefault\n : TDefault;\n\ntype PredictiveSearchQueryFragmentsForOptions<TOptions> = [\n FragmentForOptions<TOptions, \"product\", typeof DEFAULT_PRODUCT_FRAGMENT>,\n FragmentForOptions<TOptions, \"collection\", typeof DEFAULT_COLLECTION_FRAGMENT>,\n FragmentForOptions<TOptions, \"page\", typeof DEFAULT_PAGE_FRAGMENT>,\n FragmentForOptions<TOptions, \"article\", typeof DEFAULT_ARTICLE_FRAGMENT>,\n FragmentForOptions<TOptions, \"query\", typeof DEFAULT_QUERY_FRAGMENT>,\n];\n\ntype PredictiveSearchQuerySourceForOptions<TOptions> = ComposedSource<\n SourceOf<typeof PREDICTIVE_SEARCH_QUERY>,\n PredictiveSearchQueryFragmentsForOptions<TOptions>\n>;\n\ntype PredictiveSearchQueryForOptions<TOptions> = StorefrontQueryString<\n InferResult<PredictiveSearchQuerySourceForOptions<TOptions>>,\n InferVariables<PredictiveSearchQuerySourceForOptions<TOptions>>,\n PredictiveSearchQuerySourceForOptions<TOptions>\n>;\n\nexport type PredictiveSearchQueriesForOptions<TOptions> = {\n readonly predictiveSearch: PredictiveSearchQueryForOptions<TOptions>;\n};\n\nfunction assertFragmentContract(fragment: string, contract: FragmentContract): void {\n const pattern = new RegExp(`fragment\\\\s+${contract.name}\\\\s+on\\\\s+${contract.typeName}`);\n if (pattern.test(fragment)) return;\n\n throw new Error(\n `Predictive search ${contract.label} fragment must be named ${contract.name} and target ${contract.typeName}`,\n );\n}\n\nfunction resolveFragments(fragments: PredictiveSearchFragments | undefined) {\n if (fragments?.product) assertFragmentContract(fragments.product, PRODUCT_CONTRACT);\n if (fragments?.collection) assertFragmentContract(fragments.collection, COLLECTION_CONTRACT);\n if (fragments?.page) assertFragmentContract(fragments.page, PAGE_CONTRACT);\n if (fragments?.article) assertFragmentContract(fragments.article, ARTICLE_CONTRACT);\n if (fragments?.query) assertFragmentContract(fragments.query, QUERY_CONTRACT);\n\n return [\n fragments?.product ?? DEFAULT_PRODUCT_FRAGMENT,\n fragments?.collection ?? DEFAULT_COLLECTION_FRAGMENT,\n fragments?.page ?? DEFAULT_PAGE_FRAGMENT,\n fragments?.article ?? DEFAULT_ARTICLE_FRAGMENT,\n fragments?.query ?? DEFAULT_QUERY_FRAGMENT,\n ] as const;\n}\n\nexport function makePredictiveSearchQueries<\n const TOptions extends CreatePredictiveSearchQueriesOptions,\n>(options: TOptions): PredictiveSearchQueriesForOptions<TOptions>;\nexport function makePredictiveSearchQueries(): PredictiveSearchQueriesForOptions<undefined>;\nexport function makePredictiveSearchQueries(options?: CreatePredictiveSearchQueriesOptions) {\n return {\n predictiveSearch: gql(PREDICTIVE_SEARCH_QUERY, resolveFragments(options?.fragments)),\n } as PredictiveSearchQueriesForOptions<typeof options>;\n}\n\nexport const predictiveSearchQueries = makePredictiveSearchQueries();\n"],"mappings":";;;AASA,MAAA,wBAAA;AACA,MAAA,2BAAA;AACA,MAAA,qBAAA;AACA,MAAA,wBAAA;AACA,MAAA,sBAAA;AAEA,MAAA,oBAAA;AACA,MAAA,uBAAA;AACA,MAAA,iBAAA;AACA,MAAA,oBAAA;AACA,MAAA,kBAAA;AAQA,MAAA,mBAAA;CACA,OAAA;CACA,MAAA;CACA,UAAA;CACA;AAEA,MAAA,sBAAA;CACA,OAAA;CACA,MAAA;CACA,UAAA;CACA;AAEA,MAAA,gBAAA;CACA,OAAA;CACA,MAAA;CACA,UAAA;CACA;AAEA,MAAA,mBAAA;CACA,OAAA;CACA,MAAA;CACA,UAAA;CACA;AAEA,MAAA,iBAAA;CACA,OAAA;CACA,MAAA;CACA,UAAA;CACA;AAEA,MAAA,4BAAA,IAAA,kRAyBA;AAEA,MAAA,+BAAA,IAAA,kJAcA;AAEA,MAAA,yBAAA,IAAA,uGAQA;AAEA,MAAA,4BAAA,IAAA,wJAiBA;AAEA,MAAA,0BAAA,IAAA,yHAOA;AAOA,MAAA,0BAAA,IACA,wfAoBW,sBAAwB,gEAIxB,yBAA2B,oDAI3B,mBAAqB,0DAIrB,sBAAwB,uDAIxB,oBAAsB,MAIjC;CACA;CACA;CACA;CACA;CACA;CACA,CACA;AAEA,MAAA,2BAAA,IAAA,0DAIA;AAEA,MAAA,8BAAA,IAAA,gEAIA;AAEA,MAAA,wBAAA,IAAA,oDAIA;AAEA,MAAA,2BAAA,IAAA,0DAIA;AAEA,MAAA,yBAAA,IAAA,wEAIA;AAiDA,SAAA,uBAAA,UAAA,UAAA;AAEA,KAAA,IADA,OAAA,eAAA,SAAA,KAAA,YAAA,SAAA,WACA,CAAA,KAAA,SAAA,CAAA;AAEA,OAAA,IAAA,MACA,qBAAA,SAAA,MAAA,0BAAA,SAAA,KAAA,cAAA,SAAA,WACA;;AAGA,SAAA,iBAAA,WAAA;AACA,KAAA,WAAA,QAAA,wBAAA,UAAA,SAAA,iBAAA;AACA,KAAA,WAAA,WAAA,wBAAA,UAAA,YAAA,oBAAA;AACA,KAAA,WAAA,KAAA,wBAAA,UAAA,MAAA,cAAA;AACA,KAAA,WAAA,QAAA,wBAAA,UAAA,SAAA,iBAAA;AACA,KAAA,WAAA,MAAA,wBAAA,UAAA,OAAA,eAAA;AAEA,QAAA;EACA,WAAA,WAAA;EACA,WAAA,cAAA;EACA,WAAA,QAAA;EACA,WAAA,WAAA;EACA,WAAA,SAAA;EACA;;AAOA,SAAA,4BAAA,SAAA;AACA,QAAA,EACA,kBAAA,IAAA,yBAAA,iBAAA,SAAA,UAAA,CAAA,EACA;;AAGA,MAAA,0BAAA,6BAAA"}
|
|
1
|
+
{"version":3,"file":"queries.mjs","names":[],"sources":["../../../../src/core/predictive-search/queries.ts"],"sourcesContent":["import {\n gql,\n type AnyStorefrontQueryString,\n type ComposedSource,\n type SourceOf,\n type StorefrontQueryString,\n} from \"../../graphql\";\nimport type { InferResult, InferVariables } from \"../../graphql\";\n\nconst PRODUCT_FRAGMENT_NAME = \"PredictiveSearchProductFragment\";\nconst COLLECTION_FRAGMENT_NAME = \"PredictiveSearchCollectionFragment\";\nconst PAGE_FRAGMENT_NAME = \"PredictiveSearchPageFragment\";\nconst ARTICLE_FRAGMENT_NAME = \"PredictiveSearchArticleFragment\";\nconst QUERY_FRAGMENT_NAME = \"PredictiveSearchQueryFragment\";\n\nconst PRODUCT_TYPE_NAME = \"Product\";\nconst COLLECTION_TYPE_NAME = \"Collection\";\nconst PAGE_TYPE_NAME = \"Page\";\nconst ARTICLE_TYPE_NAME = \"Article\";\nconst QUERY_TYPE_NAME = \"SearchQuerySuggestion\";\n\ntype FragmentContract = {\n readonly label: string;\n readonly name: string;\n readonly typeName: string;\n};\n\nconst PRODUCT_CONTRACT = {\n label: \"product\",\n name: PRODUCT_FRAGMENT_NAME,\n typeName: PRODUCT_TYPE_NAME,\n} as const satisfies FragmentContract;\n\nconst COLLECTION_CONTRACT = {\n label: \"collection\",\n name: COLLECTION_FRAGMENT_NAME,\n typeName: COLLECTION_TYPE_NAME,\n} as const satisfies FragmentContract;\n\nconst PAGE_CONTRACT = {\n label: \"page\",\n name: PAGE_FRAGMENT_NAME,\n typeName: PAGE_TYPE_NAME,\n} as const satisfies FragmentContract;\n\nconst ARTICLE_CONTRACT = {\n label: \"article\",\n name: ARTICLE_FRAGMENT_NAME,\n typeName: ARTICLE_TYPE_NAME,\n} as const satisfies FragmentContract;\n\nconst QUERY_CONTRACT = {\n label: \"query\",\n name: QUERY_FRAGMENT_NAME,\n typeName: QUERY_TYPE_NAME,\n} as const satisfies FragmentContract;\n\nconst HYDROGEN_PRODUCT_FRAGMENT = gql(`\n fragment HydrogenPredictiveSearchProductFragment on Product {\n __typename\n id\n title\n handle\n trackingParameters\n selectedOrFirstAvailableVariant(\n selectedOptions: []\n ignoreUnknownOptions: true\n caseInsensitiveMatch: true\n ) {\n id\n image {\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n }\n }\n`);\n\nconst HYDROGEN_COLLECTION_FRAGMENT = gql(`\n fragment HydrogenPredictiveSearchCollectionFragment on Collection {\n __typename\n id\n title\n handle\n image {\n url\n altText\n width\n height\n }\n trackingParameters\n }\n`);\n\nconst HYDROGEN_PAGE_FRAGMENT = gql(`\n fragment HydrogenPredictiveSearchPageFragment on Page {\n __typename\n id\n title\n handle\n trackingParameters\n }\n`);\n\nconst HYDROGEN_ARTICLE_FRAGMENT = gql(`\n fragment HydrogenPredictiveSearchArticleFragment on Article {\n __typename\n id\n title\n handle\n blog {\n handle\n }\n image {\n url\n altText\n width\n height\n }\n trackingParameters\n }\n`);\n\nconst HYDROGEN_QUERY_FRAGMENT = gql(`\n fragment HydrogenPredictiveSearchQueryFragment on SearchQuerySuggestion {\n __typename\n text\n styledText\n trackingParameters\n }\n`);\n\n// The consumer-overridable fragment spreads (e.g. PredictiveSearchProductFragment)\n// are interpolated on purpose: those fragments only exist at runtime, and the\n// gql.tada plugin skips documents containing interpolations instead of flagging\n// unknown fragments, while TypeScript still resolves the full literal source type.\n// The resolved fragments are composed in at runtime by `makePredictiveSearchQueries`.\nconst PREDICTIVE_SEARCH_QUERY = gql(\n `query PredictiveSearch(\n $country: CountryCode\n $language: LanguageCode\n $limit: Int\n $limitScope: PredictiveSearchLimitScope\n $term: String!\n $types: [PredictiveSearchType!]\n $searchableFields: [SearchableField!]\n $unavailableProducts: SearchUnavailableProductsType\n ) @inContext(country: $country, language: $language) {\n predictiveSearch(\n limit: $limit\n limitScope: $limitScope\n query: $term\n types: $types\n searchableFields: $searchableFields\n unavailableProducts: $unavailableProducts\n ) {\n articles {\n ...HydrogenPredictiveSearchArticleFragment\n ...${ARTICLE_FRAGMENT_NAME}\n }\n collections {\n ...HydrogenPredictiveSearchCollectionFragment\n ...${COLLECTION_FRAGMENT_NAME}\n }\n pages {\n ...HydrogenPredictiveSearchPageFragment\n ...${PAGE_FRAGMENT_NAME}\n }\n products {\n ...HydrogenPredictiveSearchProductFragment\n ...${PRODUCT_FRAGMENT_NAME}\n }\n queries {\n ...HydrogenPredictiveSearchQueryFragment\n ...${QUERY_FRAGMENT_NAME}\n }\n }\n }`,\n [\n HYDROGEN_PRODUCT_FRAGMENT,\n HYDROGEN_COLLECTION_FRAGMENT,\n HYDROGEN_PAGE_FRAGMENT,\n HYDROGEN_ARTICLE_FRAGMENT,\n HYDROGEN_QUERY_FRAGMENT,\n ],\n);\n\nconst DEFAULT_PRODUCT_FRAGMENT = gql(`\n fragment PredictiveSearchProductFragment on Product {\n id\n }\n`);\n\nconst DEFAULT_COLLECTION_FRAGMENT = gql(`\n fragment PredictiveSearchCollectionFragment on Collection {\n id\n }\n`);\n\nconst DEFAULT_PAGE_FRAGMENT = gql(`\n fragment PredictiveSearchPageFragment on Page {\n id\n }\n`);\n\nconst DEFAULT_ARTICLE_FRAGMENT = gql(`\n fragment PredictiveSearchArticleFragment on Article {\n id\n }\n`);\n\nconst DEFAULT_QUERY_FRAGMENT = gql(`\n fragment PredictiveSearchQueryFragment on SearchQuerySuggestion {\n text\n }\n`);\n\n/**\n * Additional GraphQL fragments that select extra fields on each predictive search result type.\n *\n * Each fragment must be named and typed as follows, or\n * `makePredictiveSearchQueries` throws:\n *\n * - `product`: `fragment PredictiveSearchProductFragment on Product`\n * - `collection`: `fragment PredictiveSearchCollectionFragment on Collection`\n * - `page`: `fragment PredictiveSearchPageFragment on Page`\n * - `article`: `fragment PredictiveSearchArticleFragment on Article`\n * - `query`: `fragment PredictiveSearchQueryFragment on SearchQuerySuggestion`\n *\n * Hydrogen's own fields (used for URL generation, tracking, and display) are\n * always selected; custom fragments are merged alongside them.\n */\nexport type PredictiveSearchFragments = {\n readonly product?: AnyStorefrontQueryString;\n readonly collection?: AnyStorefrontQueryString;\n readonly page?: AnyStorefrontQueryString;\n readonly article?: AnyStorefrontQueryString;\n readonly query?: AnyStorefrontQueryString;\n};\n\n/** Options for {@link makePredictiveSearchQueries}. */\nexport type CreatePredictiveSearchQueriesOptions<\n TFragments extends PredictiveSearchFragments = PredictiveSearchFragments,\n> = {\n /** Extra fields to select per result type. Hydrogen's built-in fields are always included. */\n readonly fragments?: TFragments;\n};\n\ntype FragmentForOptions<\n TOptions,\n TKey extends keyof PredictiveSearchFragments,\n TDefault extends AnyStorefrontQueryString,\n> = TOptions extends { readonly fragments: infer TFragments }\n ? TFragments extends Record<TKey, infer TFragment extends AnyStorefrontQueryString>\n ? TFragment\n : TDefault\n : TDefault;\n\ntype PredictiveSearchQueryFragmentsForOptions<TOptions> = [\n FragmentForOptions<TOptions, \"product\", typeof DEFAULT_PRODUCT_FRAGMENT>,\n FragmentForOptions<TOptions, \"collection\", typeof DEFAULT_COLLECTION_FRAGMENT>,\n FragmentForOptions<TOptions, \"page\", typeof DEFAULT_PAGE_FRAGMENT>,\n FragmentForOptions<TOptions, \"article\", typeof DEFAULT_ARTICLE_FRAGMENT>,\n FragmentForOptions<TOptions, \"query\", typeof DEFAULT_QUERY_FRAGMENT>,\n];\n\ntype PredictiveSearchQuerySourceForOptions<TOptions> = ComposedSource<\n SourceOf<typeof PREDICTIVE_SEARCH_QUERY>,\n PredictiveSearchQueryFragmentsForOptions<TOptions>\n>;\n\ntype PredictiveSearchQueryForOptions<TOptions> = StorefrontQueryString<\n InferResult<PredictiveSearchQuerySourceForOptions<TOptions>>,\n InferVariables<PredictiveSearchQuerySourceForOptions<TOptions>>,\n PredictiveSearchQuerySourceForOptions<TOptions>\n>;\n\n/** Resolves the query map shape from {@link CreatePredictiveSearchQueriesOptions}, preserving full type inference from custom fragments. */\nexport type PredictiveSearchQueriesForOptions<TOptions> = {\n readonly predictiveSearch: PredictiveSearchQueryForOptions<TOptions>;\n};\n\nfunction assertFragmentContract(fragment: string, contract: FragmentContract): void {\n const pattern = new RegExp(`fragment\\\\s+${contract.name}\\\\s+on\\\\s+${contract.typeName}\\\\b`);\n if (pattern.test(fragment)) return;\n\n throw new Error(\n `Predictive search ${contract.label} fragment must be named ${contract.name} and target ${contract.typeName}`,\n );\n}\n\nfunction resolveFragments(fragments: PredictiveSearchFragments | undefined) {\n if (fragments?.product) assertFragmentContract(fragments.product, PRODUCT_CONTRACT);\n if (fragments?.collection) assertFragmentContract(fragments.collection, COLLECTION_CONTRACT);\n if (fragments?.page) assertFragmentContract(fragments.page, PAGE_CONTRACT);\n if (fragments?.article) assertFragmentContract(fragments.article, ARTICLE_CONTRACT);\n if (fragments?.query) assertFragmentContract(fragments.query, QUERY_CONTRACT);\n\n return [\n fragments?.product ?? DEFAULT_PRODUCT_FRAGMENT,\n fragments?.collection ?? DEFAULT_COLLECTION_FRAGMENT,\n fragments?.page ?? DEFAULT_PAGE_FRAGMENT,\n fragments?.article ?? DEFAULT_ARTICLE_FRAGMENT,\n fragments?.query ?? DEFAULT_QUERY_FRAGMENT,\n ] as const;\n}\n\n/**\n * Composes the predictive search GraphQL query document from the built-in\n * base query and either custom or default fragments.\n *\n * Custom fragments are validated at runtime against their expected names\n * and target GraphQL types. See {@link PredictiveSearchFragments} for the\n * naming contract.\n *\n * The returned object's `predictiveSearch` key carries the composed query\n * with full type inference from the provided fragments.\n *\n * @throws {Error} When a custom fragment does not match its required name or target type.\n */\nexport function makePredictiveSearchQueries<\n const TOptions extends CreatePredictiveSearchQueriesOptions,\n>(options: TOptions): PredictiveSearchQueriesForOptions<TOptions>;\nexport function makePredictiveSearchQueries(): PredictiveSearchQueriesForOptions<undefined>;\nexport function makePredictiveSearchQueries(options?: CreatePredictiveSearchQueriesOptions) {\n return {\n predictiveSearch: gql(PREDICTIVE_SEARCH_QUERY, resolveFragments(options?.fragments)),\n } as PredictiveSearchQueriesForOptions<typeof options>;\n}\n\n/**\n * Internal default query object built from Hydrogen's default fragments;\n * used when `query` is omitted.\n *\n * Build custom query documents with {@link makePredictiveSearchQueries}.\n */\nexport const predictiveSearchQueries = makePredictiveSearchQueries();\n"],"mappings":";;;AASA,MAAA,wBAAA;AACA,MAAA,2BAAA;AACA,MAAA,qBAAA;AACA,MAAA,wBAAA;AACA,MAAA,sBAAA;AAEA,MAAA,oBAAA;AACA,MAAA,uBAAA;AACA,MAAA,iBAAA;AACA,MAAA,oBAAA;AACA,MAAA,kBAAA;AAQA,MAAA,mBAAA;CACA,OAAA;CACA,MAAA;CACA,UAAA;CACA;AAEA,MAAA,sBAAA;CACA,OAAA;CACA,MAAA;CACA,UAAA;CACA;AAEA,MAAA,gBAAA;CACA,OAAA;CACA,MAAA;CACA,UAAA;CACA;AAEA,MAAA,mBAAA;CACA,OAAA;CACA,MAAA;CACA,UAAA;CACA;AAEA,MAAA,iBAAA;CACA,OAAA;CACA,MAAA;CACA,UAAA;CACA;AAEA,MAAA,4BAAA,IAAA,kRAyBA;AAEA,MAAA,+BAAA,IAAA,kJAcA;AAEA,MAAA,yBAAA,IAAA,uGAQA;AAEA,MAAA,4BAAA,IAAA,wJAiBA;AAEA,MAAA,0BAAA,IAAA,yHAOA;AAOA,MAAA,0BAAA,IACA,wfAoBW,sBAAwB,gEAIxB,yBAA2B,oDAI3B,mBAAqB,0DAIrB,sBAAwB,uDAIxB,oBAAsB,MAIjC;CACA;CACA;CACA;CACA;CACA;CACA,CACA;AAEA,MAAA,2BAAA,IAAA,0DAIA;AAEA,MAAA,8BAAA,IAAA,gEAIA;AAEA,MAAA,wBAAA,IAAA,oDAIA;AAEA,MAAA,2BAAA,IAAA,0DAIA;AAEA,MAAA,yBAAA,IAAA,wEAIA;AAmEA,SAAA,uBAAA,UAAA,UAAA;AAEA,KAAA,IADA,OAAA,eAAA,SAAA,KAAA,YAAA,SAAA,SAAA,KACA,CAAA,KAAA,SAAA,CAAA;AAEA,OAAA,IAAA,MACA,qBAAA,SAAA,MAAA,0BAAA,SAAA,KAAA,cAAA,SAAA,WACA;;AAGA,SAAA,iBAAA,WAAA;AACA,KAAA,WAAA,QAAA,wBAAA,UAAA,SAAA,iBAAA;AACA,KAAA,WAAA,WAAA,wBAAA,UAAA,YAAA,oBAAA;AACA,KAAA,WAAA,KAAA,wBAAA,UAAA,MAAA,cAAA;AACA,KAAA,WAAA,QAAA,wBAAA,UAAA,SAAA,iBAAA;AACA,KAAA,WAAA,MAAA,wBAAA,UAAA,OAAA,eAAA;AAEA,QAAA;EACA,WAAA,WAAA;EACA,WAAA,cAAA;EACA,WAAA,QAAA;EACA,WAAA,WAAA;EACA,WAAA,SAAA;EACA;;AAoBA,SAAA,4BAAA,SAAA;AACA,QAAA,EACA,kBAAA,IAAA,yBAAA,iBAAA,SAAA,UAAA,CAAA,EACA;;;;;;;;AASA,MAAA,0BAAA,6BAAA"}
|
|
@@ -8,24 +8,57 @@ type PredictiveSearchGraphqlResult<TQuery extends AnyStorefrontQueryString> = TQ
|
|
|
8
8
|
type PredictiveSearchItemsForQuery<TQuery extends AnyStorefrontQueryString> = NonNullable<PredictiveSearchGraphqlResult<TQuery> extends {
|
|
9
9
|
predictiveSearch?: (infer TItems) | null;
|
|
10
10
|
} ? TItems : never>;
|
|
11
|
+
/**
|
|
12
|
+
* Predictive search result payload returned by {@link queryPredictiveSearch}.
|
|
13
|
+
*
|
|
14
|
+
* The generic parameter is the shape of `items` and defaults to the
|
|
15
|
+
* built-in query's items. For custom fragments, infer the full payload from
|
|
16
|
+
* `queryPredictiveSearch` and your query document:
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* type CustomSearchData = Awaited<
|
|
21
|
+
* ReturnType<typeof queryPredictiveSearch<typeof queries.predictiveSearch>>
|
|
22
|
+
* >;
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
11
25
|
type PredictiveSearchData<TItems = PredictiveSearchItemsForQuery<typeof predictiveSearchQueries.predictiveSearch>> = {
|
|
12
|
-
term: string;
|
|
13
|
-
total: number;
|
|
26
|
+
/** Trimmed search term that produced this result. */term: string; /** Number of items returned, summed across every array in `items` (including query suggestions). Not a total match count. */
|
|
27
|
+
total: number; /** Predictive search items grouped by resource type. */
|
|
14
28
|
items: TItems;
|
|
15
29
|
};
|
|
30
|
+
/** Resolves {@link PredictiveSearchData} from a custom query document type, inferring the items shape from the query's result type. */
|
|
16
31
|
type PredictiveSearchDataForQuery<TQuery extends AnyStorefrontQueryString> = PredictiveSearchData<PredictiveSearchItemsForQuery<TQuery>>;
|
|
32
|
+
/** Resolves {@link PredictiveSearchData} from {@link CreatePredictiveSearchQueriesOptions}, reflecting the items shape produced by custom fragments. */
|
|
17
33
|
type PredictiveSearchDataForOptions<TOptions extends CreatePredictiveSearchQueriesOptions> = PredictiveSearchDataForQuery<PredictiveSearchQueriesForOptions<TOptions>["predictiveSearch"]>;
|
|
34
|
+
/**
|
|
35
|
+
* Options for {@link queryPredictiveSearch}.
|
|
36
|
+
*
|
|
37
|
+
* The generic parameter accepts a custom query document type and defaults to
|
|
38
|
+
* the built-in predictive search query.
|
|
39
|
+
*/
|
|
18
40
|
type QueryPredictiveSearchOptions<TQuery extends AnyStorefrontQueryString = typeof predictiveSearchQueries.predictiveSearch> = {
|
|
19
|
-
storefrontClient: Pick<StorefrontClient, "graphql">;
|
|
20
|
-
term: string;
|
|
21
|
-
query?: TQuery;
|
|
22
|
-
limit?: number;
|
|
23
|
-
limitScope?: PredictiveSearchLimitScope;
|
|
24
|
-
types?: PredictiveSearchType[];
|
|
25
|
-
searchableFields?: SearchableField[];
|
|
26
|
-
unavailableProducts?: SearchUnavailableProductsType;
|
|
41
|
+
/** Storefront client instance. Only the `graphql` method is used. */storefrontClient: Pick<StorefrontClient, "graphql">; /** Search term. Trimmed before use; empty or whitespace-only terms return an empty result without a network request. */
|
|
42
|
+
term: string; /** Custom GraphQL query document, usually created with `makePredictiveSearchQueries`. Must declare the same variables as the built-in query (`$term`, `$limit`, `$limitScope`, `$types`, `$searchableFields`, `$unavailableProducts`). */
|
|
43
|
+
query?: TQuery; /** Maximum result count. Truncated to an integer, then clamped to the Storefront API's accepted range (1-10). Omitted or non-finite values use Hydrogen's default of 5 (the Storefront API's own default is 10). */
|
|
44
|
+
limit?: number; /** Whether the limit applies to each resource type independently or to all types combined. Defaults to `"EACH"` (the Storefront API's own default is `"ALL"`). */
|
|
45
|
+
limitScope?: PredictiveSearchLimitScope; /** Resource types to include. When omitted, the Storefront API uses the shop's predictive search type settings, which exclude articles by default. Pass `types` explicitly to control which result arrays are populated. */
|
|
46
|
+
types?: PredictiveSearchType[]; /** Fields to search within. When omitted, the Storefront API searches its default field set (title, product type, variant title, and vendor), not every field. */
|
|
47
|
+
searchableFields?: SearchableField[]; /** How to handle unavailable products. Defaults to `"HIDE"` rather than the Storefront API's shop-specific setting (which defaults to `"LAST"`). */
|
|
48
|
+
unavailableProducts?: SearchUnavailableProductsType; /** Signal to abort the in-flight request. */
|
|
27
49
|
signal?: AbortSignal;
|
|
28
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Executes a predictive search query against the Storefront API and returns
|
|
53
|
+
* the data payload.
|
|
54
|
+
*
|
|
55
|
+
* To serve results with the Storefront API response headers (e.g. cache
|
|
56
|
+
* headers), use the `get` handler from `createPredictiveSearchServerHandlers()`.
|
|
57
|
+
*
|
|
58
|
+
* @throws {Error} When the Storefront API returns GraphQL errors.
|
|
59
|
+
* @throws {Error} When the response contains no predictive search data.
|
|
60
|
+
* @throws Errors from `storefrontClient.graphql` (e.g. network failures or an `AbortError` when `signal` aborts).
|
|
61
|
+
*/
|
|
29
62
|
declare function queryPredictiveSearch<const TQuery extends AnyStorefrontQueryString = typeof predictiveSearchQueries.predictiveSearch>(options: QueryPredictiveSearchOptions<TQuery>): Promise<PredictiveSearchDataForQuery<TQuery>>;
|
|
30
63
|
//#endregion
|
|
31
64
|
export { PredictiveSearchData, PredictiveSearchDataForOptions, QueryPredictiveSearchOptions, queryPredictiveSearch };
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { predictiveSearchQueries } from "./queries.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/core/predictive-search/search.ts
|
|
4
|
+
/** Minimum result count accepted by the Storefront API predictive search query. Values below this are clamped up. */
|
|
4
5
|
const MIN_PREDICTIVE_SEARCH_LIMIT = 1;
|
|
6
|
+
/** Maximum result count accepted by the Storefront API predictive search query. Values above this are clamped down. */
|
|
5
7
|
const MAX_PREDICTIVE_SEARCH_LIMIT = 10;
|
|
8
|
+
/** Default result count used when no limit is specified. */
|
|
6
9
|
const DEFAULT_PREDICTIVE_SEARCH_LIMIT = 5;
|
|
10
|
+
/** Default limit scope, applying the limit to each resource type independently. */
|
|
7
11
|
const DEFAULT_PREDICTIVE_SEARCH_LIMIT_SCOPE = "EACH";
|
|
12
|
+
/** Default unavailable-product behavior, hiding them from results. */
|
|
8
13
|
const DEFAULT_PREDICTIVE_SEARCH_UNAVAILABLE_PRODUCTS = "HIDE";
|
|
14
|
+
/**
|
|
15
|
+
* Returns a zero-result `PredictiveSearchData` payload with empty arrays
|
|
16
|
+
* for every resource type.
|
|
17
|
+
*
|
|
18
|
+
* Used internally for blank terms and for the client store's initial and
|
|
19
|
+
* reset state. Typed with the default items shape.
|
|
20
|
+
*/
|
|
9
21
|
function getEmptyPredictiveSearchResult(term = "") {
|
|
10
22
|
return {
|
|
11
23
|
term,
|
|
@@ -19,9 +31,31 @@ function getEmptyPredictiveSearchResult(term = "") {
|
|
|
19
31
|
}
|
|
20
32
|
};
|
|
21
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Executes a predictive search query against the Storefront API and returns
|
|
36
|
+
* the data payload.
|
|
37
|
+
*
|
|
38
|
+
* To serve results with the Storefront API response headers (e.g. cache
|
|
39
|
+
* headers), use the `get` handler from `createPredictiveSearchServerHandlers()`.
|
|
40
|
+
*
|
|
41
|
+
* @throws {Error} When the Storefront API returns GraphQL errors.
|
|
42
|
+
* @throws {Error} When the response contains no predictive search data.
|
|
43
|
+
* @throws Errors from `storefrontClient.graphql` (e.g. network failures or an `AbortError` when `signal` aborts).
|
|
44
|
+
*/
|
|
22
45
|
async function queryPredictiveSearch(options) {
|
|
23
46
|
return (await fetchPredictiveSearch(options)).data;
|
|
24
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Executes a predictive search query against the Storefront API and returns
|
|
50
|
+
* both the data payload and response headers.
|
|
51
|
+
*
|
|
52
|
+
* See `QueryPredictiveSearchOptions` for option defaults and
|
|
53
|
+
* `PredictiveSearchData` for the shape of the returned data.
|
|
54
|
+
*
|
|
55
|
+
* @throws {Error} When the Storefront API returns GraphQL errors.
|
|
56
|
+
* @throws {Error} When the response contains no predictive search data.
|
|
57
|
+
* @throws Errors from `storefrontClient.graphql` (e.g. network failures or an `AbortError` when `signal` aborts).
|
|
58
|
+
*/
|
|
25
59
|
async function fetchPredictiveSearch({ storefrontClient, term, query, limit, limitScope = DEFAULT_PREDICTIVE_SEARCH_LIMIT_SCOPE, types, searchableFields, unavailableProducts = DEFAULT_PREDICTIVE_SEARCH_UNAVAILABLE_PRODUCTS, signal }) {
|
|
26
60
|
const trimmedTerm = term.trim();
|
|
27
61
|
if (!trimmedTerm) return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.mjs","names":[],"sources":["../../../../src/core/predictive-search/search.ts"],"sourcesContent":["import type { GraphQLFormattedError, StorefrontClient } from \"../../client\";\nimport type { AnyStorefrontQueryString, StorefrontQueryString } from \"../../graphql\";\nimport type {\n PredictiveSearchLimitScope,\n PredictiveSearchType,\n SearchUnavailableProductsType,\n SearchableField,\n} from \"../../graphql/generated/storefront-api-types\";\nimport {\n predictiveSearchQueries,\n type CreatePredictiveSearchQueriesOptions,\n type PredictiveSearchQueriesForOptions,\n} from \"./queries\";\n\nexport const MIN_PREDICTIVE_SEARCH_LIMIT = 1;\nexport const MAX_PREDICTIVE_SEARCH_LIMIT = 10;\nexport const DEFAULT_PREDICTIVE_SEARCH_LIMIT = 5;\nexport const DEFAULT_PREDICTIVE_SEARCH_LIMIT_SCOPE = \"EACH\" satisfies PredictiveSearchLimitScope;\nexport const DEFAULT_PREDICTIVE_SEARCH_UNAVAILABLE_PRODUCTS =\n \"HIDE\" satisfies SearchUnavailableProductsType;\n\ntype PredictiveSearchGraphqlResult<TQuery extends AnyStorefrontQueryString> =\n TQuery extends StorefrontQueryString<infer TResult, infer _Variables, string> ? TResult : never;\n\ntype PredictiveSearchItemsForQuery<TQuery extends AnyStorefrontQueryString> = NonNullable<\n PredictiveSearchGraphqlResult<TQuery> extends { predictiveSearch?: (infer TItems) | null }\n ? TItems\n : never\n>;\n\nexport type PredictiveSearchData<\n TItems = PredictiveSearchItemsForQuery<typeof predictiveSearchQueries.predictiveSearch>,\n> = {\n term: string;\n total: number;\n items: TItems;\n};\n\nexport type PredictiveSearchDataForQuery<TQuery extends AnyStorefrontQueryString> =\n PredictiveSearchData<PredictiveSearchItemsForQuery<TQuery>>;\n\nexport type PredictiveSearchDataForOptions<TOptions extends CreatePredictiveSearchQueriesOptions> =\n PredictiveSearchDataForQuery<PredictiveSearchQueriesForOptions<TOptions>[\"predictiveSearch\"]>;\n\nexport type QueryPredictiveSearchOptions<\n TQuery extends AnyStorefrontQueryString = typeof predictiveSearchQueries.predictiveSearch,\n> = {\n storefrontClient: Pick<StorefrontClient, \"graphql\">;\n term: string;\n query?: TQuery;\n limit?: number;\n limitScope?: PredictiveSearchLimitScope;\n types?: PredictiveSearchType[];\n searchableFields?: SearchableField[];\n unavailableProducts?: SearchUnavailableProductsType;\n signal?: AbortSignal;\n};\n\ntype FetchPredictiveSearchResult<TQuery extends AnyStorefrontQueryString> = {\n data: PredictiveSearchDataForQuery<TQuery>;\n headers: Headers;\n};\n\ntype PredictiveSearchQueryDocument = AnyStorefrontQueryString;\n\ntype PredictiveSearchVariables = {\n term: string;\n limit: number;\n limitScope: PredictiveSearchLimitScope;\n types: PredictiveSearchType[] | undefined;\n searchableFields: SearchableField[] | undefined;\n unavailableProducts: SearchUnavailableProductsType;\n};\n\ntype PredictiveSearchQueryResult<TItems> = {\n data: { predictiveSearch?: TItems | null } | null;\n errors?: GraphQLFormattedError[];\n headers: Headers;\n};\n\ntype PredictiveSearchGraphql<TItems> = (\n query: PredictiveSearchQueryDocument,\n options: { variables: PredictiveSearchVariables; signal?: AbortSignal },\n) => Promise<PredictiveSearchQueryResult<TItems>>;\n\nexport function getEmptyPredictiveSearchResult(term = \"\"): PredictiveSearchData {\n return {\n term,\n total: 0,\n items: {\n products: [],\n collections: [],\n pages: [],\n articles: [],\n queries: [],\n },\n };\n}\n\nexport async function queryPredictiveSearch<\n const TQuery extends AnyStorefrontQueryString = typeof predictiveSearchQueries.predictiveSearch,\n>(options: QueryPredictiveSearchOptions<TQuery>): Promise<PredictiveSearchDataForQuery<TQuery>> {\n const result = await fetchPredictiveSearch(options);\n return result.data;\n}\n\nexport async function fetchPredictiveSearch<\n const TQuery extends AnyStorefrontQueryString = typeof predictiveSearchQueries.predictiveSearch,\n>({\n storefrontClient,\n term,\n query,\n limit,\n limitScope = DEFAULT_PREDICTIVE_SEARCH_LIMIT_SCOPE,\n types,\n searchableFields,\n unavailableProducts = DEFAULT_PREDICTIVE_SEARCH_UNAVAILABLE_PRODUCTS,\n signal,\n}: QueryPredictiveSearchOptions<TQuery>): Promise<FetchPredictiveSearchResult<TQuery>> {\n const trimmedTerm = term.trim();\n if (!trimmedTerm) {\n return {\n data: getEmptyPredictiveSearchResult(\n trimmedTerm,\n ) as unknown as PredictiveSearchDataForQuery<TQuery>,\n headers: new Headers(),\n };\n }\n\n const document = query ?? predictiveSearchQueries.predictiveSearch;\n const graphql = storefrontClient.graphql as PredictiveSearchGraphql<\n PredictiveSearchItemsForQuery<TQuery>\n >;\n const result = await graphql(document, {\n variables: {\n limit: clampPredictiveSearchLimit(limit),\n limitScope,\n term: trimmedTerm,\n types,\n searchableFields,\n unavailableProducts,\n },\n ...(signal ? { signal } : {}),\n });\n\n if (result.errors) {\n throw new Error(`Shopify API errors: ${formatGraphQLErrors(result.errors)}`);\n }\n\n const items = result.data?.predictiveSearch;\n if (!items) {\n throw new Error(\"No predictive search data returned from Shopify API\");\n }\n\n return {\n data: {\n term: trimmedTerm,\n total: countPredictiveSearchItems(items),\n items,\n } as PredictiveSearchDataForQuery<TQuery>,\n headers: result.headers,\n };\n}\n\nfunction clampPredictiveSearchLimit(limit: number | undefined): number {\n if (typeof limit !== \"number\") return DEFAULT_PREDICTIVE_SEARCH_LIMIT;\n if (!Number.isFinite(limit)) return DEFAULT_PREDICTIVE_SEARCH_LIMIT;\n\n const integerLimit = Math.trunc(limit);\n return Math.min(MAX_PREDICTIVE_SEARCH_LIMIT, Math.max(MIN_PREDICTIVE_SEARCH_LIMIT, integerLimit));\n}\n\nfunction countPredictiveSearchItems(items: unknown): number {\n if (!items || typeof items !== \"object\") return 0;\n\n return Object.values(items).reduce((total, value) => {\n if (!Array.isArray(value)) return total;\n return total + value.length;\n }, 0);\n}\n\nfunction formatGraphQLErrors(errors: GraphQLFormattedError[]): string {\n return errors.map(({ message }) => message).join(\", \");\n}\n"],"mappings":";;;AAcA,MAAa,8BAA8B;AAC3C,MAAa,8BAA8B;AAC3C,MAAa,kCAAkC;AAC/C,MAAa,wCAAwC;AACrD,MAAa,iDACX;AAkEF,SAAgB,+BAA+B,OAAO,IAA0B;AAC9E,QAAO;EACL;EACA,OAAO;EACP,OAAO;GACL,UAAU,EAAE;GACZ,aAAa,EAAE;GACf,OAAO,EAAE;GACT,UAAU,EAAE;GACZ,SAAS,EAAE;GACZ;EACF;;AAGH,eAAsB,sBAEpB,SAA8F;AAE9F,SAAO,MADc,sBAAsB,QAAQ,EACrC;;AAGhB,eAAsB,sBAEpB,EACA,kBACA,MACA,OACA,OACA,aAAa,uCACb,OACA,kBACA,sBAAsB,gDACtB,UACqF;CACrF,MAAM,cAAc,KAAK,MAAM;AAC/B,KAAI,CAAC,YACH,QAAO;EACL,MAAM,+BACJ,YACD;EACD,SAAS,IAAI,SAAS;EACvB;CAGH,MAAM,WAAW,SAAS,wBAAwB;CAClD,MAAM,UAAU,iBAAiB;CAGjC,MAAM,SAAS,MAAM,QAAQ,UAAU;EACrC,WAAW;GACT,OAAO,2BAA2B,MAAM;GACxC;GACA,MAAM;GACN;GACA;GACA;GACD;EACD,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;EAC7B,CAAC;AAEF,KAAI,OAAO,OACT,OAAM,IAAI,MAAM,uBAAuB,oBAAoB,OAAO,OAAO,GAAG;CAG9E,MAAM,QAAQ,OAAO,MAAM;AAC3B,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,sDAAsD;AAGxE,QAAO;EACL,MAAM;GACJ,MAAM;GACN,OAAO,2BAA2B,MAAM;GACxC;GACD;EACD,SAAS,OAAO;EACjB;;AAGH,SAAS,2BAA2B,OAAmC;AACrE,KAAI,OAAO,UAAU,SAAU;AAC/B,KAAI,CAAC,OAAO,SAAS,MAAM,CAAE;AAG7B,QAAO,KAAK,QAAiC,KAAK,OAD7B,KAAK,MAAM,MAC+D,CAAC,CAAC;;AAGnG,SAAS,2BAA2B,OAAwB;AAC1D,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,QAAO,OAAO,OAAO,MAAM,CAAC,QAAQ,OAAO,UAAU;AACnD,MAAI,CAAC,MAAM,QAAQ,MAAM,CAAE,QAAO;AAClC,SAAO,QAAQ,MAAM;IACpB,EAAE;;AAGP,SAAS,oBAAoB,QAAyC;AACpE,QAAO,OAAO,KAAK,EAAE,cAAc,QAAQ,CAAC,KAAK,KAAK"}
|
|
1
|
+
{"version":3,"file":"search.mjs","names":[],"sources":["../../../../src/core/predictive-search/search.ts"],"sourcesContent":["import type { GraphQLFormattedError, StorefrontClient } from \"../../client\";\nimport type { AnyStorefrontQueryString, StorefrontQueryString } from \"../../graphql\";\nimport type {\n PredictiveSearchLimitScope,\n PredictiveSearchType,\n SearchUnavailableProductsType,\n SearchableField,\n} from \"../../graphql/generated/storefront-api-types\";\nimport {\n predictiveSearchQueries,\n type CreatePredictiveSearchQueriesOptions,\n type PredictiveSearchQueriesForOptions,\n} from \"./queries\";\n\n/** Minimum result count accepted by the Storefront API predictive search query. Values below this are clamped up. */\nexport const MIN_PREDICTIVE_SEARCH_LIMIT = 1;\n/** Maximum result count accepted by the Storefront API predictive search query. Values above this are clamped down. */\nexport const MAX_PREDICTIVE_SEARCH_LIMIT = 10;\n/** Default result count used when no limit is specified. */\nexport const DEFAULT_PREDICTIVE_SEARCH_LIMIT = 5;\n/** Default limit scope, applying the limit to each resource type independently. */\nexport const DEFAULT_PREDICTIVE_SEARCH_LIMIT_SCOPE = \"EACH\" satisfies PredictiveSearchLimitScope;\n/** Default unavailable-product behavior, hiding them from results. */\nexport const DEFAULT_PREDICTIVE_SEARCH_UNAVAILABLE_PRODUCTS =\n \"HIDE\" satisfies SearchUnavailableProductsType;\n\ntype PredictiveSearchGraphqlResult<TQuery extends AnyStorefrontQueryString> =\n TQuery extends StorefrontQueryString<infer TResult, infer _Variables, string> ? TResult : never;\n\ntype PredictiveSearchItemsForQuery<TQuery extends AnyStorefrontQueryString> = NonNullable<\n PredictiveSearchGraphqlResult<TQuery> extends { predictiveSearch?: (infer TItems) | null }\n ? TItems\n : never\n>;\n\n/**\n * Predictive search result payload returned by {@link queryPredictiveSearch}.\n *\n * The generic parameter is the shape of `items` and defaults to the\n * built-in query's items. For custom fragments, infer the full payload from\n * `queryPredictiveSearch` and your query document:\n *\n * @example\n * ```ts\n * type CustomSearchData = Awaited<\n * ReturnType<typeof queryPredictiveSearch<typeof queries.predictiveSearch>>\n * >;\n * ```\n */\nexport type PredictiveSearchData<\n TItems = PredictiveSearchItemsForQuery<typeof predictiveSearchQueries.predictiveSearch>,\n> = {\n /** Trimmed search term that produced this result. */\n term: string;\n /** Number of items returned, summed across every array in `items` (including query suggestions). Not a total match count. */\n total: number;\n /** Predictive search items grouped by resource type. */\n items: TItems;\n};\n\n/** Resolves {@link PredictiveSearchData} from a custom query document type, inferring the items shape from the query's result type. */\nexport type PredictiveSearchDataForQuery<TQuery extends AnyStorefrontQueryString> =\n PredictiveSearchData<PredictiveSearchItemsForQuery<TQuery>>;\n\n/** Resolves {@link PredictiveSearchData} from {@link CreatePredictiveSearchQueriesOptions}, reflecting the items shape produced by custom fragments. */\nexport type PredictiveSearchDataForOptions<TOptions extends CreatePredictiveSearchQueriesOptions> =\n PredictiveSearchDataForQuery<PredictiveSearchQueriesForOptions<TOptions>[\"predictiveSearch\"]>;\n\n/**\n * Options for {@link queryPredictiveSearch}.\n *\n * The generic parameter accepts a custom query document type and defaults to\n * the built-in predictive search query.\n */\nexport type QueryPredictiveSearchOptions<\n TQuery extends AnyStorefrontQueryString = typeof predictiveSearchQueries.predictiveSearch,\n> = {\n /** Storefront client instance. Only the `graphql` method is used. */\n storefrontClient: Pick<StorefrontClient, \"graphql\">;\n /** Search term. Trimmed before use; empty or whitespace-only terms return an empty result without a network request. */\n term: string;\n /** Custom GraphQL query document, usually created with `makePredictiveSearchQueries`. Must declare the same variables as the built-in query (`$term`, `$limit`, `$limitScope`, `$types`, `$searchableFields`, `$unavailableProducts`). */\n query?: TQuery;\n /** Maximum result count. Truncated to an integer, then clamped to the Storefront API's accepted range (1-10). Omitted or non-finite values use Hydrogen's default of 5 (the Storefront API's own default is 10). */\n limit?: number;\n /** Whether the limit applies to each resource type independently or to all types combined. Defaults to `\"EACH\"` (the Storefront API's own default is `\"ALL\"`). */\n limitScope?: PredictiveSearchLimitScope;\n /** Resource types to include. When omitted, the Storefront API uses the shop's predictive search type settings, which exclude articles by default. Pass `types` explicitly to control which result arrays are populated. */\n types?: PredictiveSearchType[];\n /** Fields to search within. When omitted, the Storefront API searches its default field set (title, product type, variant title, and vendor), not every field. */\n searchableFields?: SearchableField[];\n /** How to handle unavailable products. Defaults to `\"HIDE\"` rather than the Storefront API's shop-specific setting (which defaults to `\"LAST\"`). */\n unavailableProducts?: SearchUnavailableProductsType;\n /** Signal to abort the in-flight request. */\n signal?: AbortSignal;\n};\n\ntype FetchPredictiveSearchResult<TQuery extends AnyStorefrontQueryString> = {\n data: PredictiveSearchDataForQuery<TQuery>;\n headers: Headers;\n};\n\ntype PredictiveSearchQueryDocument = AnyStorefrontQueryString;\n\ntype PredictiveSearchVariables = {\n term: string;\n limit: number;\n limitScope: PredictiveSearchLimitScope;\n types: PredictiveSearchType[] | undefined;\n searchableFields: SearchableField[] | undefined;\n unavailableProducts: SearchUnavailableProductsType;\n};\n\ntype PredictiveSearchQueryResult<TItems> = {\n data: { predictiveSearch?: TItems | null } | null;\n errors?: GraphQLFormattedError[];\n headers: Headers;\n};\n\ntype PredictiveSearchGraphql<TItems> = (\n query: PredictiveSearchQueryDocument,\n options: { variables: PredictiveSearchVariables; signal?: AbortSignal },\n) => Promise<PredictiveSearchQueryResult<TItems>>;\n\n/**\n * Returns a zero-result `PredictiveSearchData` payload with empty arrays\n * for every resource type.\n *\n * Used internally for blank terms and for the client store's initial and\n * reset state. Typed with the default items shape.\n */\nexport function getEmptyPredictiveSearchResult(term = \"\"): PredictiveSearchData {\n return {\n term,\n total: 0,\n items: {\n products: [],\n collections: [],\n pages: [],\n articles: [],\n queries: [],\n },\n };\n}\n\n/**\n * Executes a predictive search query against the Storefront API and returns\n * the data payload.\n *\n * To serve results with the Storefront API response headers (e.g. cache\n * headers), use the `get` handler from `createPredictiveSearchServerHandlers()`.\n *\n * @throws {Error} When the Storefront API returns GraphQL errors.\n * @throws {Error} When the response contains no predictive search data.\n * @throws Errors from `storefrontClient.graphql` (e.g. network failures or an `AbortError` when `signal` aborts).\n */\nexport async function queryPredictiveSearch<\n const TQuery extends AnyStorefrontQueryString = typeof predictiveSearchQueries.predictiveSearch,\n>(options: QueryPredictiveSearchOptions<TQuery>): Promise<PredictiveSearchDataForQuery<TQuery>> {\n const result = await fetchPredictiveSearch(options);\n return result.data;\n}\n\n/**\n * Executes a predictive search query against the Storefront API and returns\n * both the data payload and response headers.\n *\n * See `QueryPredictiveSearchOptions` for option defaults and\n * `PredictiveSearchData` for the shape of the returned data.\n *\n * @throws {Error} When the Storefront API returns GraphQL errors.\n * @throws {Error} When the response contains no predictive search data.\n * @throws Errors from `storefrontClient.graphql` (e.g. network failures or an `AbortError` when `signal` aborts).\n */\nexport async function fetchPredictiveSearch<\n const TQuery extends AnyStorefrontQueryString = typeof predictiveSearchQueries.predictiveSearch,\n>({\n storefrontClient,\n term,\n query,\n limit,\n limitScope = DEFAULT_PREDICTIVE_SEARCH_LIMIT_SCOPE,\n types,\n searchableFields,\n unavailableProducts = DEFAULT_PREDICTIVE_SEARCH_UNAVAILABLE_PRODUCTS,\n signal,\n}: QueryPredictiveSearchOptions<TQuery>): Promise<FetchPredictiveSearchResult<TQuery>> {\n const trimmedTerm = term.trim();\n if (!trimmedTerm) {\n return {\n data: getEmptyPredictiveSearchResult(\n trimmedTerm,\n ) as unknown as PredictiveSearchDataForQuery<TQuery>,\n headers: new Headers(),\n };\n }\n\n const document = query ?? predictiveSearchQueries.predictiveSearch;\n const graphql = storefrontClient.graphql as PredictiveSearchGraphql<\n PredictiveSearchItemsForQuery<TQuery>\n >;\n const result = await graphql(document, {\n variables: {\n limit: clampPredictiveSearchLimit(limit),\n limitScope,\n term: trimmedTerm,\n types,\n searchableFields,\n unavailableProducts,\n },\n ...(signal ? { signal } : {}),\n });\n\n if (result.errors) {\n throw new Error(`Shopify API errors: ${formatGraphQLErrors(result.errors)}`);\n }\n\n const items = result.data?.predictiveSearch;\n if (!items) {\n throw new Error(\"No predictive search data returned from Shopify API\");\n }\n\n return {\n data: {\n term: trimmedTerm,\n total: countPredictiveSearchItems(items),\n items,\n } as PredictiveSearchDataForQuery<TQuery>,\n headers: result.headers,\n };\n}\n\nfunction clampPredictiveSearchLimit(limit: number | undefined): number {\n if (typeof limit !== \"number\") return DEFAULT_PREDICTIVE_SEARCH_LIMIT;\n if (!Number.isFinite(limit)) return DEFAULT_PREDICTIVE_SEARCH_LIMIT;\n\n const integerLimit = Math.trunc(limit);\n return Math.min(MAX_PREDICTIVE_SEARCH_LIMIT, Math.max(MIN_PREDICTIVE_SEARCH_LIMIT, integerLimit));\n}\n\nfunction countPredictiveSearchItems(items: unknown): number {\n if (!items || typeof items !== \"object\") return 0;\n\n return Object.values(items).reduce((total, value) => {\n if (!Array.isArray(value)) return total;\n return total + value.length;\n }, 0);\n}\n\nfunction formatGraphQLErrors(errors: GraphQLFormattedError[]): string {\n return errors.map(({ message }) => message).join(\", \");\n}\n"],"mappings":";;;;AAeA,MAAa,8BAA8B;;AAE3C,MAAa,8BAA8B;;AAE3C,MAAa,kCAAkC;;AAE/C,MAAa,wCAAwC;;AAErD,MAAa,iDACX;;;;;;;;AA2GF,SAAgB,+BAA+B,OAAO,IAA0B;AAC9E,QAAO;EACL;EACA,OAAO;EACP,OAAO;GACL,UAAU,EAAE;GACZ,aAAa,EAAE;GACf,OAAO,EAAE;GACT,UAAU,EAAE;GACZ,SAAS,EAAE;GACZ;EACF;;;;;;;;;;;;;AAcH,eAAsB,sBAEpB,SAA8F;AAE9F,SAAO,MADc,sBAAsB,QAAQ,EACrC;;;;;;;;;;;;;AAchB,eAAsB,sBAEpB,EACA,kBACA,MACA,OACA,OACA,aAAa,uCACb,OACA,kBACA,sBAAsB,gDACtB,UACqF;CACrF,MAAM,cAAc,KAAK,MAAM;AAC/B,KAAI,CAAC,YACH,QAAO;EACL,MAAM,+BACJ,YACD;EACD,SAAS,IAAI,SAAS;EACvB;CAGH,MAAM,WAAW,SAAS,wBAAwB;CAClD,MAAM,UAAU,iBAAiB;CAGjC,MAAM,SAAS,MAAM,QAAQ,UAAU;EACrC,WAAW;GACT,OAAO,2BAA2B,MAAM;GACxC;GACA,MAAM;GACN;GACA;GACA;GACD;EACD,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;EAC7B,CAAC;AAEF,KAAI,OAAO,OACT,OAAM,IAAI,MAAM,uBAAuB,oBAAoB,OAAO,OAAO,GAAG;CAG9E,MAAM,QAAQ,OAAO,MAAM;AAC3B,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,sDAAsD;AAGxE,QAAO;EACL,MAAM;GACJ,MAAM;GACN,OAAO,2BAA2B,MAAM;GACxC;GACD;EACD,SAAS,OAAO;EACjB;;AAGH,SAAS,2BAA2B,OAAmC;AACrE,KAAI,OAAO,UAAU,SAAU;AAC/B,KAAI,CAAC,OAAO,SAAS,MAAM,CAAE;AAG7B,QAAO,KAAK,QAAiC,KAAK,OAD7B,KAAK,MAAM,MAC+D,CAAC,CAAC;;AAGnG,SAAS,2BAA2B,OAAwB;AAC1D,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,QAAO,OAAO,OAAO,MAAM,CAAC,QAAQ,OAAO,UAAU;AACnD,MAAI,CAAC,MAAM,QAAQ,MAAM,CAAE,QAAO;AAClC,SAAO,QAAQ,MAAM;IACpB,EAAE;;AAGP,SAAS,oBAAoB,QAAyC;AACpE,QAAO,OAAO,KAAK,EAAE,cAAc,QAAQ,CAAC,KAAK,KAAK"}
|
|
@@ -21,14 +21,40 @@ type PredictiveSearchServerHandlers<TOptions extends CreatePredictiveSearchServe
|
|
|
21
21
|
readonly [predictiveSearchServerHandlersQuery]: PredictiveSearchQueriesForOptions<TOptions>["predictiveSearch"];
|
|
22
22
|
get: PredictiveSearchGetHandler<TData>;
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Options for {@link createPredictiveSearchServerHandlers}.
|
|
26
|
+
*
|
|
27
|
+
* Extends {@link CreatePredictiveSearchQueriesOptions} with server-specific
|
|
28
|
+
* defaults. Each search option sets a default that individual requests can
|
|
29
|
+
* override via query parameters. These are defaults, not restrictions: any
|
|
30
|
+
* client can override them.
|
|
31
|
+
*/
|
|
24
32
|
type CreatePredictiveSearchServerHandlersOptions = CreatePredictiveSearchQueriesOptions & {
|
|
25
|
-
path?: string;
|
|
26
|
-
limit?: number;
|
|
27
|
-
limitScope?: PredictiveSearchLimitScope;
|
|
28
|
-
types?: PredictiveSearchType[];
|
|
29
|
-
searchableFields?: SearchableField[];
|
|
33
|
+
/** Route path the handler is registered at. Defaults to `"/api/predictive-search"`. */path?: string; /** Default result count. Requests can override it with the `limit` query parameter; empty or unparseable values fall back to this default. See {@link QueryPredictiveSearchOptions.limit} for normalization and the default when omitted. */
|
|
34
|
+
limit?: number; /** Default limit scope. Individual requests override via the `limitScope` query parameter. */
|
|
35
|
+
limitScope?: PredictiveSearchLimitScope; /** Default resource types to search. Individual requests override via the `types` query parameter (comma-separated). */
|
|
36
|
+
types?: PredictiveSearchType[]; /** Default fields to search. Individual requests override via the `searchableFields` query parameter (comma-separated). */
|
|
37
|
+
searchableFields?: SearchableField[]; /** Default unavailable-product behavior. Individual requests override via the `unavailableProducts` query parameter. */
|
|
30
38
|
unavailableProducts?: SearchUnavailableProductsType;
|
|
31
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Creates a GET request handler that serves predictive search results from
|
|
42
|
+
* the Storefront API.
|
|
43
|
+
*
|
|
44
|
+
* The handler parses search parameters from the request URL:
|
|
45
|
+
*
|
|
46
|
+
* - `q` for the search term
|
|
47
|
+
* - Each search option in {@link CreatePredictiveSearchServerHandlersOptions}
|
|
48
|
+
* can be overridden by a query parameter of the same name
|
|
49
|
+
*
|
|
50
|
+
* The `types` and `searchableFields` parameters accept comma-separated values. Invalid enum
|
|
51
|
+
* values, and any Storefront API failure, produce an error result with code
|
|
52
|
+
* `"invalid_predictive_search_request"`.
|
|
53
|
+
*
|
|
54
|
+
* The returned handlers object has a `get` method that can be registered
|
|
55
|
+
* as a route handler. Successful results include the Storefront API response
|
|
56
|
+
* headers, excluding `content-encoding`, `content-length`, and `server-timing`.
|
|
57
|
+
*/
|
|
32
58
|
declare function createPredictiveSearchServerHandlers(): PredictiveSearchServerHandlers;
|
|
33
59
|
declare function createPredictiveSearchServerHandlers<const TOptions extends CreatePredictiveSearchServerHandlersOptions>(options: TOptions): PredictiveSearchServerHandlers<TOptions>;
|
|
34
60
|
//#endregion
|
|
@@ -70,9 +70,11 @@ function parsePredictiveSearchRequest(request, options) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
function parseLimit(searchParams, options) {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
const defaultLimit = options.limit ?? 5;
|
|
74
|
+
const rawLimit = searchParams.get("limit")?.trim() ?? "";
|
|
75
|
+
if (rawLimit === "") return defaultLimit;
|
|
76
|
+
const limit = Number(rawLimit);
|
|
77
|
+
return Number.isFinite(limit) ? limit : defaultLimit;
|
|
76
78
|
}
|
|
77
79
|
function parseOne(searchParams, name, allowedValues) {
|
|
78
80
|
const value = searchParams.get(name);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-handlers.mjs","names":[],"sources":["../../../../src/core/predictive-search/server-handlers.ts"],"sourcesContent":["import type {\n PredictiveSearchLimitScope,\n PredictiveSearchType,\n SearchUnavailableProductsType,\n SearchableField,\n} from \"../../graphql/generated/storefront-api-types\";\nimport { createProxyResponseHeaders } from \"../request-routing/interceptors/proxy\";\nimport type {\n CallableRouteHandler,\n ShopifyRouteError,\n ShopifyRouteErrorResult,\n ShopifyRouteJsonResult,\n} from \"../request-routing/registered-routes\";\nimport { createCallableRouteHandler } from \"../request-routing/registered-routes\";\nimport { PREDICTIVE_SEARCH_API_PATH, PREDICTIVE_SEARCH_GET_METHOD } from \"./constants\";\nimport {\n makePredictiveSearchQueries,\n predictiveSearchQueries,\n type CreatePredictiveSearchQueriesOptions,\n type PredictiveSearchQueriesForOptions,\n} from \"./queries\";\nimport {\n DEFAULT_PREDICTIVE_SEARCH_LIMIT,\n DEFAULT_PREDICTIVE_SEARCH_LIMIT_SCOPE,\n DEFAULT_PREDICTIVE_SEARCH_UNAVAILABLE_PRODUCTS,\n fetchPredictiveSearch,\n type PredictiveSearchDataForOptions,\n type QueryPredictiveSearchOptions,\n} from \"./search\";\n\nconst predictiveSearchServerHandlersQuery: unique symbol = Symbol(\"hydrogen.predictiveSearchQuery\");\n\nconst VALID_LIMIT_SCOPES: readonly PredictiveSearchLimitScope[] = [\"ALL\", \"EACH\"];\nconst VALID_PREDICTIVE_SEARCH_TYPES: readonly PredictiveSearchType[] = [\n \"ARTICLE\",\n \"COLLECTION\",\n \"PAGE\",\n \"PRODUCT\",\n \"QUERY\",\n];\nconst VALID_SEARCHABLE_FIELDS: readonly SearchableField[] = [\n \"AUTHOR\",\n \"BODY\",\n \"PRODUCT_TYPE\",\n \"TAG\",\n \"TITLE\",\n \"VARIANTS_BARCODE\",\n \"VARIANTS_SKU\",\n \"VARIANTS_TITLE\",\n \"VENDOR\",\n];\nconst VALID_UNAVAILABLE_PRODUCTS: readonly SearchUnavailableProductsType[] = [\n \"HIDE\",\n \"LAST\",\n \"SHOW\",\n];\n\ntype PredictiveSearchHandlerContext = {\n request: Request;\n storefrontClient: QueryPredictiveSearchOptions[\"storefrontClient\"];\n};\n\ntype PredictiveSearchGetData<TData = PredictiveSearchDataForOptions<{}>> = TData;\ntype PredictiveSearchGetResult<TData = PredictiveSearchDataForOptions<{}>> =\n | ShopifyRouteJsonResult<PredictiveSearchGetData<TData>>\n | ShopifyRouteErrorResult<PredictiveSearchError>;\n\ntype PredictiveSearchErrorCode = \"invalid_predictive_search_request\";\ntype PredictiveSearchError = ShopifyRouteError & {\n code: PredictiveSearchErrorCode;\n};\n\ntype PredictiveSearchGetHandler<TData = PredictiveSearchDataForOptions<{}>> = CallableRouteHandler<\n PredictiveSearchHandlerContext,\n PredictiveSearchGetResult<TData>,\n string,\n typeof PREDICTIVE_SEARCH_GET_METHOD\n>;\n\ntype PredictiveSearchServerHandlers<\n TOptions extends CreatePredictiveSearchServerHandlersOptions = {},\n TData = PredictiveSearchDataForOptions<TOptions>,\n> = {\n readonly [predictiveSearchServerHandlersQuery]: PredictiveSearchQueriesForOptions<TOptions>[\"predictiveSearch\"];\n get: PredictiveSearchGetHandler<TData>;\n};\n\nexport type CreatePredictiveSearchServerHandlersOptions = CreatePredictiveSearchQueriesOptions & {\n path?: string;\n limit?: number;\n limitScope?: PredictiveSearchLimitScope;\n types?: PredictiveSearchType[];\n searchableFields?: SearchableField[];\n unavailableProducts?: SearchUnavailableProductsType;\n};\n\nexport function createPredictiveSearchServerHandlers(): PredictiveSearchServerHandlers;\nexport function createPredictiveSearchServerHandlers<\n const TOptions extends CreatePredictiveSearchServerHandlersOptions,\n>(options: TOptions): PredictiveSearchServerHandlers<TOptions>;\nexport function createPredictiveSearchServerHandlers(\n options: CreatePredictiveSearchServerHandlersOptions = {},\n): PredictiveSearchServerHandlers<CreatePredictiveSearchServerHandlersOptions> {\n const queries = options.fragments\n ? makePredictiveSearchQueries(options)\n : predictiveSearchQueries;\n const handler = createCallableRouteHandler(\n options.path ?? PREDICTIVE_SEARCH_API_PATH,\n PREDICTIVE_SEARCH_GET_METHOD,\n (context: PredictiveSearchHandlerContext) => handleGet(context, options, queries),\n );\n const handlers = {\n get: handler,\n } as PredictiveSearchServerHandlers<CreatePredictiveSearchServerHandlersOptions>;\n\n Object.defineProperty(handlers, predictiveSearchServerHandlersQuery, {\n value: queries.predictiveSearch,\n });\n return handlers;\n}\n\nasync function handleGet(\n { request, storefrontClient }: PredictiveSearchHandlerContext,\n options: CreatePredictiveSearchServerHandlersOptions,\n queries: PredictiveSearchQueriesForOptions<CreatePredictiveSearchServerHandlersOptions>,\n): Promise<PredictiveSearchGetResult> {\n let searchOptions: ParsedPredictiveSearchRequest;\n try {\n searchOptions = parsePredictiveSearchRequest(request, options);\n } catch (error) {\n return errorResult(getErrorMessage(error, \"Invalid predictive search request\"));\n }\n\n try {\n const result = await fetchPredictiveSearch({\n storefrontClient,\n query: queries.predictiveSearch,\n ...searchOptions,\n });\n return {\n type: \"json\",\n data: result.data,\n headers: createProxyResponseHeaders(result.headers),\n };\n } catch (error) {\n return errorResult(getErrorMessage(error, \"Predictive search request failed\"));\n }\n}\n\ntype ParsedPredictiveSearchRequest = Omit<\n QueryPredictiveSearchOptions,\n \"storefrontClient\" | \"query\" | \"signal\"\n>;\n\nfunction parsePredictiveSearchRequest(\n request: Request,\n options: CreatePredictiveSearchServerHandlersOptions,\n): ParsedPredictiveSearchRequest {\n const searchParams = new URL(request.url).searchParams;\n return {\n term: searchParams.get(\"q\") ?? \"\",\n limit: parseLimit(searchParams, options),\n limitScope:\n parseOne(searchParams, \"limitScope\", VALID_LIMIT_SCOPES) ??\n options.limitScope ??\n DEFAULT_PREDICTIVE_SEARCH_LIMIT_SCOPE,\n types: parseMany(searchParams, \"types\", VALID_PREDICTIVE_SEARCH_TYPES) ?? options.types,\n searchableFields:\n parseMany(searchParams, \"searchableFields\", VALID_SEARCHABLE_FIELDS) ??\n options.searchableFields,\n unavailableProducts:\n parseOne(searchParams, \"unavailableProducts\", VALID_UNAVAILABLE_PRODUCTS) ??\n options.unavailableProducts ??\n DEFAULT_PREDICTIVE_SEARCH_UNAVAILABLE_PRODUCTS,\n };\n}\n\nfunction parseLimit(\n searchParams: URLSearchParams,\n options: CreatePredictiveSearchServerHandlersOptions,\n): number {\n const rawLimit = searchParams.get(\"limit\");\n if (rawLimit === null) return options.limit ?? DEFAULT_PREDICTIVE_SEARCH_LIMIT;\n return Number(rawLimit);\n}\n\nfunction parseOne<TValue extends string>(\n searchParams: URLSearchParams,\n name: string,\n allowedValues: readonly TValue[],\n): TValue | undefined {\n const value = searchParams.get(name);\n if (value === null || value === \"\") return undefined;\n if (isAllowedValue(value, allowedValues)) return value;\n throw new Error(`Invalid ${name} value \"${value}\".`);\n}\n\nfunction parseMany<TValue extends string>(\n searchParams: URLSearchParams,\n name: string,\n allowedValues: readonly TValue[],\n): TValue[] | undefined {\n const values = splitParamValues(searchParams, name);\n if (values.length === 0) return undefined;\n return values.map((value) => parseAllowedValue(name, value, allowedValues));\n}\n\nfunction splitParamValues(searchParams: URLSearchParams, name: string): string[] {\n return searchParams\n .getAll(name)\n .flatMap((value) => value.split(\",\"))\n .map((value) => value.trim())\n .filter(Boolean);\n}\n\nfunction parseAllowedValue<TValue extends string>(\n name: string,\n value: string,\n allowedValues: readonly TValue[],\n): TValue {\n if (isAllowedValue(value, allowedValues)) return value;\n throw new Error(`Invalid ${name} value \"${value}\".`);\n}\n\nfunction isAllowedValue<TValue extends string>(\n value: string,\n allowedValues: readonly TValue[],\n): value is TValue {\n return allowedValues.some((allowedValue) => allowedValue === value);\n}\n\nfunction errorResult(message: string): ShopifyRouteErrorResult<PredictiveSearchError> {\n return {\n type: \"error\",\n error: {\n code: \"invalid_predictive_search_request\",\n message,\n },\n };\n}\n\nfunction getErrorMessage(error: unknown, fallback: string): string {\n return error instanceof Error ? error.message : fallback;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server-handlers.mjs","names":[],"sources":["../../../../src/core/predictive-search/server-handlers.ts"],"sourcesContent":["import type {\n PredictiveSearchLimitScope,\n PredictiveSearchType,\n SearchUnavailableProductsType,\n SearchableField,\n} from \"../../graphql/generated/storefront-api-types\";\nimport { createProxyResponseHeaders } from \"../request-routing/interceptors/proxy\";\nimport type {\n CallableRouteHandler,\n ShopifyRouteError,\n ShopifyRouteErrorResult,\n ShopifyRouteJsonResult,\n} from \"../request-routing/registered-routes\";\nimport { createCallableRouteHandler } from \"../request-routing/registered-routes\";\nimport { PREDICTIVE_SEARCH_API_PATH, PREDICTIVE_SEARCH_GET_METHOD } from \"./constants\";\nimport {\n makePredictiveSearchQueries,\n predictiveSearchQueries,\n type CreatePredictiveSearchQueriesOptions,\n type PredictiveSearchQueriesForOptions,\n} from \"./queries\";\nimport {\n DEFAULT_PREDICTIVE_SEARCH_LIMIT,\n DEFAULT_PREDICTIVE_SEARCH_LIMIT_SCOPE,\n DEFAULT_PREDICTIVE_SEARCH_UNAVAILABLE_PRODUCTS,\n fetchPredictiveSearch,\n type PredictiveSearchDataForOptions,\n type QueryPredictiveSearchOptions,\n} from \"./search\";\n\nconst predictiveSearchServerHandlersQuery: unique symbol = Symbol(\"hydrogen.predictiveSearchQuery\");\n\nconst VALID_LIMIT_SCOPES: readonly PredictiveSearchLimitScope[] = [\"ALL\", \"EACH\"];\nconst VALID_PREDICTIVE_SEARCH_TYPES: readonly PredictiveSearchType[] = [\n \"ARTICLE\",\n \"COLLECTION\",\n \"PAGE\",\n \"PRODUCT\",\n \"QUERY\",\n];\nconst VALID_SEARCHABLE_FIELDS: readonly SearchableField[] = [\n \"AUTHOR\",\n \"BODY\",\n \"PRODUCT_TYPE\",\n \"TAG\",\n \"TITLE\",\n \"VARIANTS_BARCODE\",\n \"VARIANTS_SKU\",\n \"VARIANTS_TITLE\",\n \"VENDOR\",\n];\nconst VALID_UNAVAILABLE_PRODUCTS: readonly SearchUnavailableProductsType[] = [\n \"HIDE\",\n \"LAST\",\n \"SHOW\",\n];\n\ntype PredictiveSearchHandlerContext = {\n request: Request;\n storefrontClient: QueryPredictiveSearchOptions[\"storefrontClient\"];\n};\n\ntype PredictiveSearchGetData<TData = PredictiveSearchDataForOptions<{}>> = TData;\ntype PredictiveSearchGetResult<TData = PredictiveSearchDataForOptions<{}>> =\n | ShopifyRouteJsonResult<PredictiveSearchGetData<TData>>\n | ShopifyRouteErrorResult<PredictiveSearchError>;\n\ntype PredictiveSearchErrorCode = \"invalid_predictive_search_request\";\ntype PredictiveSearchError = ShopifyRouteError & {\n code: PredictiveSearchErrorCode;\n};\n\ntype PredictiveSearchGetHandler<TData = PredictiveSearchDataForOptions<{}>> = CallableRouteHandler<\n PredictiveSearchHandlerContext,\n PredictiveSearchGetResult<TData>,\n string,\n typeof PREDICTIVE_SEARCH_GET_METHOD\n>;\n\ntype PredictiveSearchServerHandlers<\n TOptions extends CreatePredictiveSearchServerHandlersOptions = {},\n TData = PredictiveSearchDataForOptions<TOptions>,\n> = {\n readonly [predictiveSearchServerHandlersQuery]: PredictiveSearchQueriesForOptions<TOptions>[\"predictiveSearch\"];\n get: PredictiveSearchGetHandler<TData>;\n};\n\n/**\n * Options for {@link createPredictiveSearchServerHandlers}.\n *\n * Extends {@link CreatePredictiveSearchQueriesOptions} with server-specific\n * defaults. Each search option sets a default that individual requests can\n * override via query parameters. These are defaults, not restrictions: any\n * client can override them.\n */\nexport type CreatePredictiveSearchServerHandlersOptions = CreatePredictiveSearchQueriesOptions & {\n /** Route path the handler is registered at. Defaults to `\"/api/predictive-search\"`. */\n path?: string;\n /** Default result count. Requests can override it with the `limit` query parameter; empty or unparseable values fall back to this default. See {@link QueryPredictiveSearchOptions.limit} for normalization and the default when omitted. */\n limit?: number;\n /** Default limit scope. Individual requests override via the `limitScope` query parameter. */\n limitScope?: PredictiveSearchLimitScope;\n /** Default resource types to search. Individual requests override via the `types` query parameter (comma-separated). */\n types?: PredictiveSearchType[];\n /** Default fields to search. Individual requests override via the `searchableFields` query parameter (comma-separated). */\n searchableFields?: SearchableField[];\n /** Default unavailable-product behavior. Individual requests override via the `unavailableProducts` query parameter. */\n unavailableProducts?: SearchUnavailableProductsType;\n};\n\n/**\n * Creates a GET request handler that serves predictive search results from\n * the Storefront API.\n *\n * The handler parses search parameters from the request URL:\n *\n * - `q` for the search term\n * - Each search option in {@link CreatePredictiveSearchServerHandlersOptions}\n * can be overridden by a query parameter of the same name\n *\n * The `types` and `searchableFields` parameters accept comma-separated values. Invalid enum\n * values, and any Storefront API failure, produce an error result with code\n * `\"invalid_predictive_search_request\"`.\n *\n * The returned handlers object has a `get` method that can be registered\n * as a route handler. Successful results include the Storefront API response\n * headers, excluding `content-encoding`, `content-length`, and `server-timing`.\n */\nexport function createPredictiveSearchServerHandlers(): PredictiveSearchServerHandlers;\nexport function createPredictiveSearchServerHandlers<\n const TOptions extends CreatePredictiveSearchServerHandlersOptions,\n>(options: TOptions): PredictiveSearchServerHandlers<TOptions>;\nexport function createPredictiveSearchServerHandlers(\n options: CreatePredictiveSearchServerHandlersOptions = {},\n): PredictiveSearchServerHandlers<CreatePredictiveSearchServerHandlersOptions> {\n const queries = options.fragments\n ? makePredictiveSearchQueries(options)\n : predictiveSearchQueries;\n const handler = createCallableRouteHandler(\n options.path ?? PREDICTIVE_SEARCH_API_PATH,\n PREDICTIVE_SEARCH_GET_METHOD,\n (context: PredictiveSearchHandlerContext) => handleGet(context, options, queries),\n );\n const handlers = {\n get: handler,\n } as PredictiveSearchServerHandlers<CreatePredictiveSearchServerHandlersOptions>;\n\n Object.defineProperty(handlers, predictiveSearchServerHandlersQuery, {\n value: queries.predictiveSearch,\n });\n return handlers;\n}\n\nasync function handleGet(\n { request, storefrontClient }: PredictiveSearchHandlerContext,\n options: CreatePredictiveSearchServerHandlersOptions,\n queries: PredictiveSearchQueriesForOptions<CreatePredictiveSearchServerHandlersOptions>,\n): Promise<PredictiveSearchGetResult> {\n let searchOptions: ParsedPredictiveSearchRequest;\n try {\n searchOptions = parsePredictiveSearchRequest(request, options);\n } catch (error) {\n return errorResult(getErrorMessage(error, \"Invalid predictive search request\"));\n }\n\n try {\n const result = await fetchPredictiveSearch({\n storefrontClient,\n query: queries.predictiveSearch,\n ...searchOptions,\n });\n return {\n type: \"json\",\n data: result.data,\n headers: createProxyResponseHeaders(result.headers),\n };\n } catch (error) {\n return errorResult(getErrorMessage(error, \"Predictive search request failed\"));\n }\n}\n\ntype ParsedPredictiveSearchRequest = Omit<\n QueryPredictiveSearchOptions,\n \"storefrontClient\" | \"query\" | \"signal\"\n>;\n\nfunction parsePredictiveSearchRequest(\n request: Request,\n options: CreatePredictiveSearchServerHandlersOptions,\n): ParsedPredictiveSearchRequest {\n const searchParams = new URL(request.url).searchParams;\n return {\n term: searchParams.get(\"q\") ?? \"\",\n limit: parseLimit(searchParams, options),\n limitScope:\n parseOne(searchParams, \"limitScope\", VALID_LIMIT_SCOPES) ??\n options.limitScope ??\n DEFAULT_PREDICTIVE_SEARCH_LIMIT_SCOPE,\n types: parseMany(searchParams, \"types\", VALID_PREDICTIVE_SEARCH_TYPES) ?? options.types,\n searchableFields:\n parseMany(searchParams, \"searchableFields\", VALID_SEARCHABLE_FIELDS) ??\n options.searchableFields,\n unavailableProducts:\n parseOne(searchParams, \"unavailableProducts\", VALID_UNAVAILABLE_PRODUCTS) ??\n options.unavailableProducts ??\n DEFAULT_PREDICTIVE_SEARCH_UNAVAILABLE_PRODUCTS,\n };\n}\n\n// Invalid enum params return a typed error, but an unparseable limit falls back to the configured default.\nfunction parseLimit(\n searchParams: URLSearchParams,\n options: CreatePredictiveSearchServerHandlersOptions,\n): number {\n const defaultLimit = options.limit ?? DEFAULT_PREDICTIVE_SEARCH_LIMIT;\n const rawLimit = searchParams.get(\"limit\")?.trim() ?? \"\";\n if (rawLimit === \"\") return defaultLimit;\n\n const limit = Number(rawLimit);\n return Number.isFinite(limit) ? limit : defaultLimit;\n}\n\nfunction parseOne<TValue extends string>(\n searchParams: URLSearchParams,\n name: string,\n allowedValues: readonly TValue[],\n): TValue | undefined {\n const value = searchParams.get(name);\n if (value === null || value === \"\") return undefined;\n if (isAllowedValue(value, allowedValues)) return value;\n throw new Error(`Invalid ${name} value \"${value}\".`);\n}\n\nfunction parseMany<TValue extends string>(\n searchParams: URLSearchParams,\n name: string,\n allowedValues: readonly TValue[],\n): TValue[] | undefined {\n const values = splitParamValues(searchParams, name);\n if (values.length === 0) return undefined;\n return values.map((value) => parseAllowedValue(name, value, allowedValues));\n}\n\nfunction splitParamValues(searchParams: URLSearchParams, name: string): string[] {\n return searchParams\n .getAll(name)\n .flatMap((value) => value.split(\",\"))\n .map((value) => value.trim())\n .filter(Boolean);\n}\n\nfunction parseAllowedValue<TValue extends string>(\n name: string,\n value: string,\n allowedValues: readonly TValue[],\n): TValue {\n if (isAllowedValue(value, allowedValues)) return value;\n throw new Error(`Invalid ${name} value \"${value}\".`);\n}\n\nfunction isAllowedValue<TValue extends string>(\n value: string,\n allowedValues: readonly TValue[],\n): value is TValue {\n return allowedValues.some((allowedValue) => allowedValue === value);\n}\n\nfunction errorResult(message: string): ShopifyRouteErrorResult<PredictiveSearchError> {\n return {\n type: \"error\",\n error: {\n code: \"invalid_predictive_search_request\",\n message,\n },\n };\n}\n\nfunction getErrorMessage(error: unknown, fallback: string): string {\n return error instanceof Error ? error.message : fallback;\n}\n"],"mappings":"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"}
|
|
@@ -38,7 +38,9 @@ type PredictiveSearchStore<TData extends PredictiveSearchData = PredictiveSearch
|
|
|
38
38
|
clear(): void; /** Cancels pending work and pauses store updates until reconnect. */
|
|
39
39
|
destroy(): void;
|
|
40
40
|
};
|
|
41
|
+
/** Search methods exposed by the predictive search store. */
|
|
42
|
+
type PredictiveSearchActions = Pick<PredictiveSearchStore, "search" | "clear">;
|
|
41
43
|
declare function createPredictiveSearchStore<TData extends PredictiveSearchData = PredictiveSearchData>(options?: CreatePredictiveSearchStoreOptions): PredictiveSearchStore<TData>;
|
|
42
44
|
//#endregion
|
|
43
|
-
export { CreatePredictiveSearchStoreOptions, PredictiveSearchState, PredictiveSearchStatus, PredictiveSearchStore, createPredictiveSearchStore };
|
|
45
|
+
export { CreatePredictiveSearchStoreOptions, PredictiveSearchActions, PredictiveSearchState, PredictiveSearchStatus, PredictiveSearchStore, createPredictiveSearchStore };
|
|
44
46
|
//# sourceMappingURL=store.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.mjs","names":[],"sources":["../../../../src/core/predictive-search/store.ts"],"sourcesContent":["import type {\n PredictiveSearchLimitScope,\n PredictiveSearchType,\n SearchUnavailableProductsType,\n SearchableField,\n} from \"../../graphql/generated/storefront-api-types\";\nimport { createObservable } from \"../observable\";\nimport { PREDICTIVE_SEARCH_API_PATH, PREDICTIVE_SEARCH_QUERY_PARAM } from \"./constants\";\nimport { getEmptyPredictiveSearchResult, type PredictiveSearchData } from \"./search\";\n\n/** Default delay before predictive search sends a request after a term changes. */\nexport const DEFAULT_PREDICTIVE_SEARCH_DEBOUNCE_IN_MS = 150;\n\n/** Default minimum trimmed term length required before predictive search sends a request. */\nconst DEFAULT_PREDICTIVE_SEARCH_MIN_TERM_LENGTH = 1;\n\n/** Lifecycle state for the predictive search client store. */\nexport type PredictiveSearchStatus = \"idle\" | \"loading\" | \"success\" | \"error\";\n\n/** Current predictive search snapshot exposed to UI bindings. */\nexport type PredictiveSearchState<TData extends PredictiveSearchData = PredictiveSearchData> = {\n /** Latest trimmed search term known to the store. */\n term: string;\n /** Request lifecycle state for the current term. */\n status: PredictiveSearchStatus;\n /** Predictive search data for the current term, or an empty result while idle. */\n result: TData;\n /** Error message for the latest failed request, or null when there is no active error. */\n error: string | null;\n};\n\n/** Options for creating a framework-neutral predictive search client store. */\nexport type CreatePredictiveSearchStoreOptions = {\n /**\n * Same-origin JSON endpoint used for browser predictive-search requests.\n *\n * The endpoint must return JSON matching the data payload from\n * `await predictiveSearchHandlers.get(...)`.\n */\n predictiveSearchEndpoint?: string;\n /** Delay before sending a request after the term changes. */\n debounceInMs?: number;\n /** Minimum trimmed term length required before searching. */\n minTermLength?: number;\n /** Fetch implementation, injectable for tests and non-browser runtimes. */\n fetch?: typeof globalThis.fetch;\n /** Maximum result count requested from the Storefront API. */\n limit?: number;\n /** Whether `limit` applies to each result type or all result types combined. */\n limitScope?: PredictiveSearchLimitScope;\n /** Storefront API result types to include in predictive search. */\n types?: PredictiveSearchType[];\n /** Storefront API fields to search. */\n searchableFields?: SearchableField[];\n /** Storefront API unavailable-product behavior. */\n unavailableProducts?: SearchUnavailableProductsType;\n};\n\n/** Framework-neutral predictive search store used by UI bindings. */\nexport type PredictiveSearchStore<TData extends PredictiveSearchData = PredictiveSearchData> = {\n /** Reactivates store updates after destroy(). */\n connect(): void;\n /** Returns the current predictive search state snapshot. */\n getState(): PredictiveSearchState<TData>;\n /** Subscribes to state changes and returns an unsubscribe callback. */\n subscribe(listener: (state: PredictiveSearchState<TData>) => void): () => void;\n /** Searches for a term using the configured debounce and request lifecycle. */\n search(term: string): Promise<void>;\n /** Cancels pending work and resets the store to an empty state. */\n clear(): void;\n /** Cancels pending work and pauses store updates until reconnect. */\n destroy(): void;\n};\n\ntype PredictiveSearchStoreContext<TData extends PredictiveSearchData> = {\n observable: ReturnType<typeof createObservable<PredictiveSearchState<TData>>>;\n // API route used to fetch predictive search results.\n predictiveSearchEndpoint: string;\n // Delay before firing a search request after the term changes.\n debounceInMs: number;\n // Minimum term length required before searching.\n minTermLength: number;\n // Fetch implementation, injectable for tests and non-browser runtimes.\n fetch: typeof globalThis.fetch;\n // Storefront API search options forwarded to the server handler.\n searchOptions: StoreSearchOptions;\n abortController: AbortController | null;\n // Active debounce timer waiting to start the next search.\n debounceTimer: ReturnType<typeof setTimeout> | null;\n // Monotonic counter used to ignore responses from older requests.\n requestId: number;\n // Resolver for the current debounced search promise.\n pendingResolve: (() => void) | null;\n // Prevents future work after the store has been torn down.\n destroyed: boolean;\n};\n\ntype StoreSearchOptions = Pick<\n CreatePredictiveSearchStoreOptions,\n \"limit\" | \"limitScope\" | \"types\" | \"searchableFields\" | \"unavailableProducts\"\n>;\n\nexport function createPredictiveSearchStore<\n TData extends PredictiveSearchData = PredictiveSearchData,\n>(options: CreatePredictiveSearchStoreOptions = {}): PredictiveSearchStore<TData> {\n // Prefer an injected fetch for tests and non-browser runtimes, then fall back to the ambient runtime fetch.\n const resolvedFetch = options.fetch ?? resolveGlobalFetch();\n if (typeof resolvedFetch !== \"function\") {\n throw new Error(\n \"No fetch function available. Pass a fetch option or ensure globalThis.fetch exists.\",\n );\n }\n\n const context: PredictiveSearchStoreContext<TData> = {\n observable: createObservable(createInitialState<TData>()),\n predictiveSearchEndpoint: options.predictiveSearchEndpoint ?? PREDICTIVE_SEARCH_API_PATH,\n debounceInMs: sanitizeDebounceInMs(options.debounceInMs),\n minTermLength: sanitizeMinTermLength(options.minTermLength),\n fetch: resolvedFetch,\n searchOptions: {\n limit: options.limit,\n limitScope: options.limitScope,\n types: options.types,\n searchableFields: options.searchableFields,\n unavailableProducts: options.unavailableProducts,\n },\n abortController: null,\n debounceTimer: null,\n requestId: 0,\n pendingResolve: null,\n destroyed: false,\n };\n\n return {\n connect: () => connect(context),\n getState: () => context.observable.state,\n subscribe: (listener) => context.observable.subscribe(listener),\n search: (term) => search(context, term),\n clear: () => clear(context),\n destroy: () => destroy(context),\n };\n}\n\nfunction connect<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.destroyed = false;\n}\n\nfunction resolveGlobalFetch(): typeof globalThis.fetch | undefined {\n if (typeof globalThis.fetch !== \"function\") return undefined;\n return globalThis.fetch.bind(globalThis);\n}\n\nfunction sanitizeDebounceInMs(debounceInMs: number | undefined): number {\n if (typeof debounceInMs !== \"number\") return DEFAULT_PREDICTIVE_SEARCH_DEBOUNCE_IN_MS;\n if (!Number.isFinite(debounceInMs)) return DEFAULT_PREDICTIVE_SEARCH_DEBOUNCE_IN_MS;\n if (debounceInMs < 0) return DEFAULT_PREDICTIVE_SEARCH_DEBOUNCE_IN_MS;\n return Math.trunc(debounceInMs);\n}\n\nfunction sanitizeMinTermLength(minTermLength: number | undefined): number {\n if (typeof minTermLength !== \"number\") return DEFAULT_PREDICTIVE_SEARCH_MIN_TERM_LENGTH;\n if (!Number.isFinite(minTermLength)) return DEFAULT_PREDICTIVE_SEARCH_MIN_TERM_LENGTH;\n if (minTermLength < 0) return DEFAULT_PREDICTIVE_SEARCH_MIN_TERM_LENGTH;\n return Math.trunc(minTermLength);\n}\n\nfunction createInitialState<TData extends PredictiveSearchData>(): PredictiveSearchState<TData> {\n return {\n term: \"\",\n status: \"idle\",\n result: getEmptyPredictiveSearchResult(\"\") as TData,\n error: null,\n };\n}\n\nfunction search<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n term: string,\n): Promise<void> {\n resolvePending(context);\n cancelScheduledWork(context);\n\n if (context.destroyed) return Promise.resolve();\n\n const trimmedTerm = term.trim();\n if (trimmedTerm.length < context.minTermLength) {\n resetState(context, trimmedTerm);\n return Promise.resolve();\n }\n\n return new Promise((resolve) => {\n context.pendingResolve = resolve;\n context.requestId += 1;\n context.observable.setState({\n ...context.observable.state,\n term: trimmedTerm,\n status: \"loading\",\n error: null,\n });\n scheduleSearch(context, trimmedTerm, context.requestId);\n });\n}\n\nfunction clear<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.requestId += 1;\n resolvePending(context);\n cancelScheduledWork(context);\n resetState(context, \"\");\n}\n\nfunction destroy<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.destroyed = true;\n context.requestId += 1;\n resolvePending(context);\n cancelScheduledWork(context);\n}\n\nfunction scheduleSearch<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n term: string,\n requestId: number,\n): void {\n if (context.debounceInMs === 0) {\n void executeSearch(context, term, requestId);\n return;\n }\n\n context.debounceTimer = setTimeout(() => {\n context.debounceTimer = null;\n void executeSearch(context, term, requestId);\n }, context.debounceInMs);\n}\n\nasync function executeSearch<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n term: string,\n requestId: number,\n): Promise<void> {\n const abortController = new AbortController();\n context.abortController = abortController;\n\n try {\n const response = await context.fetch(buildRequestUrl(context, term), {\n signal: abortController.signal,\n });\n const result = await readResponse<TData>(response);\n settleSuccess(context, requestId, result);\n } catch (error) {\n if (isAbortError(error)) return;\n settleError(context, requestId, getErrorMessage(error));\n }\n}\n\nasync function readResponse<TData extends PredictiveSearchData>(\n response: Response,\n): Promise<TData> {\n const body = await readJson(response);\n if (response.ok) return body as TData;\n throw new Error(getResponseErrorMessage(body, response));\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return null;\n }\n}\n\nfunction settleSuccess<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n requestId: number,\n result: TData,\n): void {\n if (!isActiveRequest(context, requestId)) return;\n context.observable.setState({\n term: result.term,\n status: \"success\",\n result,\n error: null,\n });\n finishActiveRequest(context);\n}\n\nfunction settleError<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n requestId: number,\n message: string,\n): void {\n if (!isActiveRequest(context, requestId)) return;\n context.observable.setState({\n ...context.observable.state,\n status: \"error\",\n error: message,\n });\n finishActiveRequest(context);\n}\n\nfunction finishActiveRequest<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.abortController = null;\n resolvePending(context);\n}\n\nfunction resetState<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n term: string,\n): void {\n context.observable.setState({\n term,\n status: \"idle\",\n result: getEmptyPredictiveSearchResult(term) as TData,\n error: null,\n });\n}\n\nfunction cancelScheduledWork<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n clearDebounceTimer(context);\n abortRequest(context);\n}\n\nfunction clearDebounceTimer<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n if (context.debounceTimer === null) return;\n clearTimeout(context.debounceTimer);\n context.debounceTimer = null;\n}\n\nfunction abortRequest<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.abortController?.abort();\n context.abortController = null;\n}\n\nfunction resolvePending<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.pendingResolve?.();\n context.pendingResolve = null;\n}\n\nfunction isActiveRequest<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n requestId: number,\n): boolean {\n return !context.destroyed && context.requestId === requestId;\n}\n\nfunction buildRequestUrl<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n term: string,\n): string {\n const url = new URL(context.predictiveSearchEndpoint, \"https://hydrogen.local\");\n url.searchParams.set(PREDICTIVE_SEARCH_QUERY_PARAM, term);\n appendSearchOptions(url.searchParams, context.searchOptions);\n if (URL.canParse(context.predictiveSearchEndpoint)) return url.href;\n return `${url.pathname}${url.search}`;\n}\n\nfunction appendSearchOptions(searchParams: URLSearchParams, options: StoreSearchOptions): void {\n setOptionalParam(searchParams, \"limit\", options.limit);\n setOptionalParam(searchParams, \"limitScope\", options.limitScope);\n setOptionalParam(searchParams, \"types\", options.types?.join(\",\"));\n setOptionalParam(searchParams, \"searchableFields\", options.searchableFields?.join(\",\"));\n setOptionalParam(searchParams, \"unavailableProducts\", options.unavailableProducts);\n}\n\nfunction setOptionalParam(\n searchParams: URLSearchParams,\n name: string,\n value: number | string | undefined,\n): void {\n if (value === undefined) return;\n searchParams.set(name, String(value));\n}\n\nfunction getResponseErrorMessage(body: unknown, response: Response): string {\n const message = getNestedErrorMessage(body);\n if (message) return message;\n return `Predictive search request failed with status ${response.status}`;\n}\n\nfunction getNestedErrorMessage(body: unknown): string | null {\n if (!body || typeof body !== \"object\") return null;\n if (!(\"error\" in body)) return null;\n\n const error = body.error;\n if (!error || typeof error !== \"object\") return null;\n if (!(\"message\" in error)) return null;\n\n return typeof error.message === \"string\" ? error.message : null;\n}\n\nfunction getErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : \"Predictive search request failed\";\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === \"AbortError\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"store.mjs","names":[],"sources":["../../../../src/core/predictive-search/store.ts"],"sourcesContent":["import type {\n PredictiveSearchLimitScope,\n PredictiveSearchType,\n SearchUnavailableProductsType,\n SearchableField,\n} from \"../../graphql/generated/storefront-api-types\";\nimport { createObservable } from \"../observable\";\nimport { PREDICTIVE_SEARCH_API_PATH, PREDICTIVE_SEARCH_QUERY_PARAM } from \"./constants\";\nimport { getEmptyPredictiveSearchResult, type PredictiveSearchData } from \"./search\";\n\n/** Default delay before predictive search sends a request after a term changes. */\nexport const DEFAULT_PREDICTIVE_SEARCH_DEBOUNCE_IN_MS = 150;\n\n/** Default minimum trimmed term length required before predictive search sends a request. */\nconst DEFAULT_PREDICTIVE_SEARCH_MIN_TERM_LENGTH = 1;\n\n/** Lifecycle state for the predictive search client store. */\nexport type PredictiveSearchStatus = \"idle\" | \"loading\" | \"success\" | \"error\";\n\n/** Current predictive search snapshot exposed to UI bindings. */\nexport type PredictiveSearchState<TData extends PredictiveSearchData = PredictiveSearchData> = {\n /** Latest trimmed search term known to the store. */\n term: string;\n /** Request lifecycle state for the current term. */\n status: PredictiveSearchStatus;\n /** Predictive search data for the current term, or an empty result while idle. */\n result: TData;\n /** Error message for the latest failed request, or null when there is no active error. */\n error: string | null;\n};\n\n/** Options for creating a framework-neutral predictive search client store. */\nexport type CreatePredictiveSearchStoreOptions = {\n /**\n * Same-origin JSON endpoint used for browser predictive-search requests.\n *\n * The endpoint must return JSON matching the data payload from\n * `await predictiveSearchHandlers.get(...)`.\n */\n predictiveSearchEndpoint?: string;\n /** Delay before sending a request after the term changes. */\n debounceInMs?: number;\n /** Minimum trimmed term length required before searching. */\n minTermLength?: number;\n /** Fetch implementation, injectable for tests and non-browser runtimes. */\n fetch?: typeof globalThis.fetch;\n /** Maximum result count requested from the Storefront API. */\n limit?: number;\n /** Whether `limit` applies to each result type or all result types combined. */\n limitScope?: PredictiveSearchLimitScope;\n /** Storefront API result types to include in predictive search. */\n types?: PredictiveSearchType[];\n /** Storefront API fields to search. */\n searchableFields?: SearchableField[];\n /** Storefront API unavailable-product behavior. */\n unavailableProducts?: SearchUnavailableProductsType;\n};\n\n/** Framework-neutral predictive search store used by UI bindings. */\nexport type PredictiveSearchStore<TData extends PredictiveSearchData = PredictiveSearchData> = {\n /** Reactivates store updates after destroy(). */\n connect(): void;\n /** Returns the current predictive search state snapshot. */\n getState(): PredictiveSearchState<TData>;\n /** Subscribes to state changes and returns an unsubscribe callback. */\n subscribe(listener: (state: PredictiveSearchState<TData>) => void): () => void;\n /** Searches for a term using the configured debounce and request lifecycle. */\n search(term: string): Promise<void>;\n /** Cancels pending work and resets the store to an empty state. */\n clear(): void;\n /** Cancels pending work and pauses store updates until reconnect. */\n destroy(): void;\n};\n\n/** Search methods exposed by the predictive search store. */\nexport type PredictiveSearchActions = Pick<PredictiveSearchStore, \"search\" | \"clear\">;\n\ntype PredictiveSearchStoreContext<TData extends PredictiveSearchData> = {\n observable: ReturnType<typeof createObservable<PredictiveSearchState<TData>>>;\n // API route used to fetch predictive search results.\n predictiveSearchEndpoint: string;\n // Delay before firing a search request after the term changes.\n debounceInMs: number;\n // Minimum term length required before searching.\n minTermLength: number;\n // Fetch implementation, injectable for tests and non-browser runtimes.\n fetch: typeof globalThis.fetch;\n // Storefront API search options forwarded to the server handler.\n searchOptions: StoreSearchOptions;\n abortController: AbortController | null;\n // Active debounce timer waiting to start the next search.\n debounceTimer: ReturnType<typeof setTimeout> | null;\n // Monotonic counter used to ignore responses from older requests.\n requestId: number;\n // Resolver for the current debounced search promise.\n pendingResolve: (() => void) | null;\n // Prevents future work after the store has been torn down.\n destroyed: boolean;\n};\n\ntype StoreSearchOptions = Pick<\n CreatePredictiveSearchStoreOptions,\n \"limit\" | \"limitScope\" | \"types\" | \"searchableFields\" | \"unavailableProducts\"\n>;\n\nexport function createPredictiveSearchStore<\n TData extends PredictiveSearchData = PredictiveSearchData,\n>(options: CreatePredictiveSearchStoreOptions = {}): PredictiveSearchStore<TData> {\n // Prefer an injected fetch for tests and non-browser runtimes, then fall back to the ambient runtime fetch.\n const resolvedFetch = options.fetch ?? resolveGlobalFetch();\n if (typeof resolvedFetch !== \"function\") {\n throw new Error(\n \"No fetch function available. Pass a fetch option or ensure globalThis.fetch exists.\",\n );\n }\n\n const context: PredictiveSearchStoreContext<TData> = {\n observable: createObservable(createInitialState<TData>()),\n predictiveSearchEndpoint: options.predictiveSearchEndpoint ?? PREDICTIVE_SEARCH_API_PATH,\n debounceInMs: sanitizeDebounceInMs(options.debounceInMs),\n minTermLength: sanitizeMinTermLength(options.minTermLength),\n fetch: resolvedFetch,\n searchOptions: {\n limit: options.limit,\n limitScope: options.limitScope,\n types: options.types,\n searchableFields: options.searchableFields,\n unavailableProducts: options.unavailableProducts,\n },\n abortController: null,\n debounceTimer: null,\n requestId: 0,\n pendingResolve: null,\n destroyed: false,\n };\n\n return {\n connect: () => connect(context),\n getState: () => context.observable.state,\n subscribe: (listener) => context.observable.subscribe(listener),\n search: (term) => search(context, term),\n clear: () => clear(context),\n destroy: () => destroy(context),\n };\n}\n\nfunction connect<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.destroyed = false;\n}\n\nfunction resolveGlobalFetch(): typeof globalThis.fetch | undefined {\n if (typeof globalThis.fetch !== \"function\") return undefined;\n return globalThis.fetch.bind(globalThis);\n}\n\nfunction sanitizeDebounceInMs(debounceInMs: number | undefined): number {\n if (typeof debounceInMs !== \"number\") return DEFAULT_PREDICTIVE_SEARCH_DEBOUNCE_IN_MS;\n if (!Number.isFinite(debounceInMs)) return DEFAULT_PREDICTIVE_SEARCH_DEBOUNCE_IN_MS;\n if (debounceInMs < 0) return DEFAULT_PREDICTIVE_SEARCH_DEBOUNCE_IN_MS;\n return Math.trunc(debounceInMs);\n}\n\nfunction sanitizeMinTermLength(minTermLength: number | undefined): number {\n if (typeof minTermLength !== \"number\") return DEFAULT_PREDICTIVE_SEARCH_MIN_TERM_LENGTH;\n if (!Number.isFinite(minTermLength)) return DEFAULT_PREDICTIVE_SEARCH_MIN_TERM_LENGTH;\n if (minTermLength < 0) return DEFAULT_PREDICTIVE_SEARCH_MIN_TERM_LENGTH;\n return Math.trunc(minTermLength);\n}\n\nfunction createInitialState<TData extends PredictiveSearchData>(): PredictiveSearchState<TData> {\n return {\n term: \"\",\n status: \"idle\",\n result: getEmptyPredictiveSearchResult(\"\") as TData,\n error: null,\n };\n}\n\nfunction search<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n term: string,\n): Promise<void> {\n resolvePending(context);\n cancelScheduledWork(context);\n\n if (context.destroyed) return Promise.resolve();\n\n const trimmedTerm = term.trim();\n if (trimmedTerm.length < context.minTermLength) {\n resetState(context, trimmedTerm);\n return Promise.resolve();\n }\n\n return new Promise((resolve) => {\n context.pendingResolve = resolve;\n context.requestId += 1;\n context.observable.setState({\n ...context.observable.state,\n term: trimmedTerm,\n status: \"loading\",\n error: null,\n });\n scheduleSearch(context, trimmedTerm, context.requestId);\n });\n}\n\nfunction clear<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.requestId += 1;\n resolvePending(context);\n cancelScheduledWork(context);\n resetState(context, \"\");\n}\n\nfunction destroy<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.destroyed = true;\n context.requestId += 1;\n resolvePending(context);\n cancelScheduledWork(context);\n}\n\nfunction scheduleSearch<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n term: string,\n requestId: number,\n): void {\n if (context.debounceInMs === 0) {\n void executeSearch(context, term, requestId);\n return;\n }\n\n context.debounceTimer = setTimeout(() => {\n context.debounceTimer = null;\n void executeSearch(context, term, requestId);\n }, context.debounceInMs);\n}\n\nasync function executeSearch<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n term: string,\n requestId: number,\n): Promise<void> {\n const abortController = new AbortController();\n context.abortController = abortController;\n\n try {\n const response = await context.fetch(buildRequestUrl(context, term), {\n signal: abortController.signal,\n });\n const result = await readResponse<TData>(response);\n settleSuccess(context, requestId, result);\n } catch (error) {\n if (isAbortError(error)) return;\n settleError(context, requestId, getErrorMessage(error));\n }\n}\n\nasync function readResponse<TData extends PredictiveSearchData>(\n response: Response,\n): Promise<TData> {\n const body = await readJson(response);\n if (response.ok) return body as TData;\n throw new Error(getResponseErrorMessage(body, response));\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return null;\n }\n}\n\nfunction settleSuccess<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n requestId: number,\n result: TData,\n): void {\n if (!isActiveRequest(context, requestId)) return;\n context.observable.setState({\n term: result.term,\n status: \"success\",\n result,\n error: null,\n });\n finishActiveRequest(context);\n}\n\nfunction settleError<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n requestId: number,\n message: string,\n): void {\n if (!isActiveRequest(context, requestId)) return;\n context.observable.setState({\n ...context.observable.state,\n status: \"error\",\n error: message,\n });\n finishActiveRequest(context);\n}\n\nfunction finishActiveRequest<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.abortController = null;\n resolvePending(context);\n}\n\nfunction resetState<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n term: string,\n): void {\n context.observable.setState({\n term,\n status: \"idle\",\n result: getEmptyPredictiveSearchResult(term) as TData,\n error: null,\n });\n}\n\nfunction cancelScheduledWork<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n clearDebounceTimer(context);\n abortRequest(context);\n}\n\nfunction clearDebounceTimer<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n if (context.debounceTimer === null) return;\n clearTimeout(context.debounceTimer);\n context.debounceTimer = null;\n}\n\nfunction abortRequest<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.abortController?.abort();\n context.abortController = null;\n}\n\nfunction resolvePending<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n): void {\n context.pendingResolve?.();\n context.pendingResolve = null;\n}\n\nfunction isActiveRequest<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n requestId: number,\n): boolean {\n return !context.destroyed && context.requestId === requestId;\n}\n\nfunction buildRequestUrl<TData extends PredictiveSearchData>(\n context: PredictiveSearchStoreContext<TData>,\n term: string,\n): string {\n const url = new URL(context.predictiveSearchEndpoint, \"https://hydrogen.local\");\n url.searchParams.set(PREDICTIVE_SEARCH_QUERY_PARAM, term);\n appendSearchOptions(url.searchParams, context.searchOptions);\n if (URL.canParse(context.predictiveSearchEndpoint)) return url.href;\n return `${url.pathname}${url.search}`;\n}\n\nfunction appendSearchOptions(searchParams: URLSearchParams, options: StoreSearchOptions): void {\n setOptionalParam(searchParams, \"limit\", options.limit);\n setOptionalParam(searchParams, \"limitScope\", options.limitScope);\n setOptionalParam(searchParams, \"types\", options.types?.join(\",\"));\n setOptionalParam(searchParams, \"searchableFields\", options.searchableFields?.join(\",\"));\n setOptionalParam(searchParams, \"unavailableProducts\", options.unavailableProducts);\n}\n\nfunction setOptionalParam(\n searchParams: URLSearchParams,\n name: string,\n value: number | string | undefined,\n): void {\n if (value === undefined) return;\n searchParams.set(name, String(value));\n}\n\nfunction getResponseErrorMessage(body: unknown, response: Response): string {\n const message = getNestedErrorMessage(body);\n if (message) return message;\n return `Predictive search request failed with status ${response.status}`;\n}\n\nfunction getNestedErrorMessage(body: unknown): string | null {\n if (!body || typeof body !== \"object\") return null;\n if (!(\"error\" in body)) return null;\n\n const error = body.error;\n if (!error || typeof error !== \"object\") return null;\n if (!(\"message\" in error)) return null;\n\n return typeof error.message === \"string\" ? error.message : null;\n}\n\nfunction getErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : \"Predictive search request failed\";\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === \"AbortError\";\n}\n"],"mappings":"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"}
|