@shopify/hydrogen 2026.10.0-preview.4 → 2026.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (437) hide show
  1. package/dist/cli/index.mjs +23 -12
  2. package/dist/client/client.d.mts +18 -15
  3. package/dist/client/client.mjs +8 -3
  4. package/dist/client/client.mjs.map +1 -1
  5. package/dist/client/errors.d.mts +35 -13
  6. package/dist/client/errors.mjs +23 -13
  7. package/dist/client/errors.mjs.map +1 -1
  8. package/dist/client/types.d.mts +140 -90
  9. package/dist/core/analytics/cart-tracker.d.mts +19 -10
  10. package/dist/core/analytics/cart-tracker.mjs +18 -10
  11. package/dist/core/analytics/cart-tracker.mjs.map +1 -1
  12. package/dist/core/analytics/events.d.mts +17 -11
  13. package/dist/core/analytics/events.mjs +13 -4
  14. package/dist/core/analytics/events.mjs.map +1 -1
  15. package/dist/core/analytics/types.d.mts +207 -118
  16. package/dist/core/analytics/utils/flatten-connection.d.mts +11 -9
  17. package/dist/core/analytics/utils/flatten-connection.mjs +7 -6
  18. package/dist/core/analytics/utils/flatten-connection.mjs.map +1 -1
  19. package/dist/core/cache/fetch-with-cache.d.mts +24 -4
  20. package/dist/core/cache/fetch-with-cache.mjs +17 -2
  21. package/dist/core/cache/fetch-with-cache.mjs.map +1 -1
  22. package/dist/core/cache/key.d.mts +2 -0
  23. package/dist/core/cache/key.mjs.map +1 -1
  24. package/dist/core/cache/run-with-cache.d.mts +56 -8
  25. package/dist/core/cache/run-with-cache.mjs +17 -6
  26. package/dist/core/cache/run-with-cache.mjs.map +1 -1
  27. package/dist/core/cache/store.d.mts +8 -6
  28. package/dist/core/cache/store.mjs.map +1 -1
  29. package/dist/core/cache/strategies.d.mts +27 -2
  30. package/dist/core/cache/strategies.mjs +6 -0
  31. package/dist/core/cache/strategies.mjs.map +1 -1
  32. package/dist/core/cart/actions.d.mts +37 -30
  33. package/dist/core/cart/actions.mjs +21 -9
  34. package/dist/core/cart/actions.mjs.map +1 -1
  35. package/dist/core/cart/attach-quantity-input.d.mts +8 -7
  36. package/dist/core/cart/attach-quantity-input.mjs +8 -7
  37. package/dist/core/cart/attach-quantity-input.mjs.map +1 -1
  38. package/dist/core/cart/buyer-identity-sync.d.mts +7 -6
  39. package/dist/core/cart/buyer-identity-sync.mjs.map +1 -1
  40. package/dist/core/cart/cart.d.mts +59 -46
  41. package/dist/core/cart/cart.mjs +46 -29
  42. package/dist/core/cart/cart.mjs.map +1 -1
  43. package/dist/core/cart/cookie.d.mts +8 -4
  44. package/dist/core/cart/cookie.mjs +8 -4
  45. package/dist/core/cart/cookie.mjs.map +1 -1
  46. package/dist/core/cart/form.d.mts +34 -20
  47. package/dist/core/cart/form.mjs +6 -4
  48. package/dist/core/cart/form.mjs.map +1 -1
  49. package/dist/core/cart/get-cart.d.mts +27 -15
  50. package/dist/core/cart/get-cart.mjs +13 -8
  51. package/dist/core/cart/get-cart.mjs.map +1 -1
  52. package/dist/core/cart/quantity.d.mts +16 -5
  53. package/dist/core/cart/quantity.mjs +16 -5
  54. package/dist/core/cart/quantity.mjs.map +1 -1
  55. package/dist/core/cart/queries.d.mts +25 -19
  56. package/dist/core/cart/queries.mjs +15 -1
  57. package/dist/core/cart/queries.mjs.map +1 -1
  58. package/dist/core/cart/server-handlers.d.mts +82 -45
  59. package/dist/core/cart/server-handlers.mjs +4 -0
  60. package/dist/core/cart/server-handlers.mjs.map +1 -1
  61. package/dist/core/cart/state.d.mts +110 -64
  62. package/dist/core/cart/state.mjs +24 -6
  63. package/dist/core/cart/state.mjs.map +1 -1
  64. package/dist/core/collection/collection.d.mts +52 -53
  65. package/dist/core/collection/collection.mjs +5 -3
  66. package/dist/core/collection/collection.mjs.map +1 -1
  67. package/dist/core/collection/reconciler.d.mts +23 -16
  68. package/dist/core/collection/reconciler.mjs +8 -4
  69. package/dist/core/collection/reconciler.mjs.map +1 -1
  70. package/dist/core/collection/state.d.mts +31 -26
  71. package/dist/core/collection/state.mjs +4 -3
  72. package/dist/core/collection/state.mjs.map +1 -1
  73. package/dist/core/collection/url.d.mts +70 -29
  74. package/dist/core/collection/url.mjs +63 -25
  75. package/dist/core/collection/url.mjs.map +1 -1
  76. package/dist/core/constants.d.mts +1 -0
  77. package/dist/core/constants.mjs +1 -0
  78. package/dist/core/constants.mjs.map +1 -1
  79. package/dist/core/logging/logging.d.mts +14 -8
  80. package/dist/core/logging/logging.mjs +15 -10
  81. package/dist/core/logging/logging.mjs.map +1 -1
  82. package/dist/core/logging/types.d.mts +28 -11
  83. package/dist/core/logging/types.mjs.map +1 -1
  84. package/dist/core/money/format.d.mts +6 -4
  85. package/dist/core/money/format.mjs +19 -9
  86. package/dist/core/money/format.mjs.map +1 -1
  87. package/dist/core/money/types.d.mts +41 -25
  88. package/dist/core/predictive-search/form.d.mts +37 -7
  89. package/dist/core/predictive-search/form.mjs +21 -3
  90. package/dist/core/predictive-search/form.mjs.map +1 -1
  91. package/dist/core/predictive-search/queries.d.mts +18 -30
  92. package/dist/core/predictive-search/queries.mjs +7 -3
  93. package/dist/core/predictive-search/queries.mjs.map +1 -1
  94. package/dist/core/predictive-search/search.d.mts +25 -27
  95. package/dist/core/predictive-search/search.mjs +20 -19
  96. package/dist/core/predictive-search/search.mjs.map +1 -1
  97. package/dist/core/predictive-search/server-handlers.d.mts +25 -26
  98. package/dist/core/predictive-search/server-handlers.mjs +4 -0
  99. package/dist/core/predictive-search/server-handlers.mjs.map +1 -1
  100. package/dist/core/predictive-search/store.d.mts +65 -24
  101. package/dist/core/predictive-search/store.mjs +12 -1
  102. package/dist/core/predictive-search/store.mjs.map +1 -1
  103. package/dist/core/predictive-search/url.d.mts +36 -37
  104. package/dist/core/predictive-search/url.mjs +14 -6
  105. package/dist/core/predictive-search/url.mjs.map +1 -1
  106. package/dist/core/product/form.d.mts +63 -16
  107. package/dist/core/product/form.mjs +8 -2
  108. package/dist/core/product/form.mjs.map +1 -1
  109. package/dist/core/product/options.d.mts +8 -10
  110. package/dist/core/product/options.mjs +8 -10
  111. package/dist/core/product/options.mjs.map +1 -1
  112. package/dist/core/product/product-form.d.mts +77 -29
  113. package/dist/core/product/product-form.mjs +32 -10
  114. package/dist/core/product/product-form.mjs.map +1 -1
  115. package/dist/core/product/state.d.mts +55 -32
  116. package/dist/core/product/url.d.mts +19 -13
  117. package/dist/core/product/url.mjs +10 -10
  118. package/dist/core/product/url.mjs.map +1 -1
  119. package/dist/core/request-context.d.mts +77 -19
  120. package/dist/core/request-context.mjs +5 -1
  121. package/dist/core/request-context.mjs.map +1 -1
  122. package/dist/core/request-routing/handle-shopify-redirects.d.mts +12 -4
  123. package/dist/core/request-routing/handle-shopify-redirects.mjs +9 -2
  124. package/dist/core/request-routing/handle-shopify-redirects.mjs.map +1 -1
  125. package/dist/core/request-routing/handle-shopify-routes.d.mts +5 -5
  126. package/dist/core/request-routing/handle-shopify-routes.mjs +7 -5
  127. package/dist/core/request-routing/handle-shopify-routes.mjs.map +1 -1
  128. package/dist/core/request-routing/interceptors/proxy.mjs +79 -43
  129. package/dist/core/request-routing/interceptors/proxy.mjs.map +1 -1
  130. package/dist/core/request-routing/interceptors/ucp-profile-proxy.mjs +69 -0
  131. package/dist/core/request-routing/interceptors/ucp-profile-proxy.mjs.map +1 -0
  132. package/dist/core/request-routing/interceptors/url-redirects.mjs +6 -1
  133. package/dist/core/request-routing/interceptors/url-redirects.mjs.map +1 -1
  134. package/dist/core/request-routing/interceptors/well-known.mjs +1 -1
  135. package/dist/core/request-routing/interceptors/well-known.mjs.map +1 -1
  136. package/dist/core/request-routing/registered-routes.d.mts +12 -0
  137. package/dist/core/request-routing/registered-routes.mjs +12 -0
  138. package/dist/core/request-routing/registered-routes.mjs.map +1 -1
  139. package/dist/core/request-routing/route-types.d.mts +43 -18
  140. package/dist/core/shop-pay/shop-pay.d.mts +71 -35
  141. package/dist/core/shop-pay/shop-pay.mjs +36 -10
  142. package/dist/core/shop-pay/shop-pay.mjs.map +1 -1
  143. package/dist/core/shopify-scripts/index.d.mts +15 -5
  144. package/dist/core/shopify-scripts/index.mjs +15 -5
  145. package/dist/core/shopify-scripts/index.mjs.map +1 -1
  146. package/dist/core/shopify-scripts/initialize.d.mts +12 -4
  147. package/dist/core/shopify-scripts/initialize.mjs +12 -4
  148. package/dist/core/shopify-scripts/initialize.mjs.map +1 -1
  149. package/dist/core/shopify-scripts/types.d.mts +58 -25
  150. package/dist/core/standard-routes/build.d.mts +11 -12
  151. package/dist/core/standard-routes/build.mjs +11 -12
  152. package/dist/core/standard-routes/build.mjs.map +1 -1
  153. package/dist/core/standard-routes/types.d.mts +30 -24
  154. package/dist/core/url.mjs +2 -1
  155. package/dist/core/url.mjs.map +1 -1
  156. package/dist/customer-account/client.d.mts +36 -69
  157. package/dist/customer-account/client.mjs +7 -8
  158. package/dist/customer-account/client.mjs.map +1 -1
  159. package/dist/customer-account/errors.d.mts +45 -36
  160. package/dist/customer-account/errors.mjs +21 -36
  161. package/dist/customer-account/errors.mjs.map +1 -1
  162. package/dist/customer-account/graphql.d.mts +11 -23
  163. package/dist/customer-account/graphql.mjs +3 -9
  164. package/dist/customer-account/graphql.mjs.map +1 -1
  165. package/dist/customer-account/session.d.mts +130 -100
  166. package/dist/customer-account/session.mjs +32 -10
  167. package/dist/customer-account/session.mjs.map +1 -1
  168. package/dist/customer-account/type-resolver.d.mts +7 -8
  169. package/dist/development/client/client.d.mts +18 -15
  170. package/dist/development/client/client.mjs +8 -3
  171. package/dist/development/client/client.mjs.map +1 -1
  172. package/dist/development/client/errors.d.mts +35 -13
  173. package/dist/development/client/errors.mjs +23 -13
  174. package/dist/development/client/errors.mjs.map +1 -1
  175. package/dist/development/client/types.d.mts +140 -90
  176. package/dist/development/core/analytics/cart-tracker.d.mts +19 -10
  177. package/dist/development/core/analytics/cart-tracker.mjs +18 -10
  178. package/dist/development/core/analytics/cart-tracker.mjs.map +1 -1
  179. package/dist/development/core/analytics/events.d.mts +17 -11
  180. package/dist/development/core/analytics/events.mjs +13 -4
  181. package/dist/development/core/analytics/events.mjs.map +1 -1
  182. package/dist/development/core/analytics/types.d.mts +207 -118
  183. package/dist/development/core/analytics/utils/flatten-connection.d.mts +11 -9
  184. package/dist/development/core/analytics/utils/flatten-connection.mjs +7 -6
  185. package/dist/development/core/analytics/utils/flatten-connection.mjs.map +1 -1
  186. package/dist/development/core/cache/fetch-with-cache.d.mts +24 -4
  187. package/dist/development/core/cache/fetch-with-cache.mjs +17 -2
  188. package/dist/development/core/cache/fetch-with-cache.mjs.map +1 -1
  189. package/dist/development/core/cache/key.d.mts +2 -0
  190. package/dist/development/core/cache/key.mjs.map +1 -1
  191. package/dist/development/core/cache/run-with-cache.d.mts +56 -8
  192. package/dist/development/core/cache/run-with-cache.mjs +17 -6
  193. package/dist/development/core/cache/run-with-cache.mjs.map +1 -1
  194. package/dist/development/core/cache/store.d.mts +8 -6
  195. package/dist/development/core/cache/store.mjs.map +1 -1
  196. package/dist/development/core/cache/strategies.d.mts +27 -2
  197. package/dist/development/core/cache/strategies.mjs +6 -0
  198. package/dist/development/core/cache/strategies.mjs.map +1 -1
  199. package/dist/development/core/cart/actions.d.mts +37 -30
  200. package/dist/development/core/cart/actions.mjs +21 -9
  201. package/dist/development/core/cart/actions.mjs.map +1 -1
  202. package/dist/development/core/cart/attach-quantity-input.d.mts +8 -7
  203. package/dist/development/core/cart/attach-quantity-input.mjs +8 -7
  204. package/dist/development/core/cart/attach-quantity-input.mjs.map +1 -1
  205. package/dist/development/core/cart/buyer-identity-sync.d.mts +6 -5
  206. package/dist/development/core/cart/buyer-identity-sync.mjs.map +1 -1
  207. package/dist/development/core/cart/cart.d.mts +59 -46
  208. package/dist/development/core/cart/cart.mjs +46 -29
  209. package/dist/development/core/cart/cart.mjs.map +1 -1
  210. package/dist/development/core/cart/cookie.d.mts +8 -4
  211. package/dist/development/core/cart/cookie.mjs +8 -4
  212. package/dist/development/core/cart/cookie.mjs.map +1 -1
  213. package/dist/development/core/cart/form.d.mts +34 -20
  214. package/dist/development/core/cart/form.mjs +6 -4
  215. package/dist/development/core/cart/form.mjs.map +1 -1
  216. package/dist/development/core/cart/get-cart.d.mts +27 -15
  217. package/dist/development/core/cart/get-cart.mjs +13 -8
  218. package/dist/development/core/cart/get-cart.mjs.map +1 -1
  219. package/dist/development/core/cart/quantity.d.mts +16 -5
  220. package/dist/development/core/cart/quantity.mjs +16 -5
  221. package/dist/development/core/cart/quantity.mjs.map +1 -1
  222. package/dist/development/core/cart/queries.d.mts +25 -19
  223. package/dist/development/core/cart/queries.mjs +15 -1
  224. package/dist/development/core/cart/queries.mjs.map +1 -1
  225. package/dist/development/core/cart/server-handlers.d.mts +82 -45
  226. package/dist/development/core/cart/server-handlers.mjs +4 -0
  227. package/dist/development/core/cart/server-handlers.mjs.map +1 -1
  228. package/dist/development/core/cart/state.d.mts +110 -64
  229. package/dist/development/core/cart/state.mjs +24 -6
  230. package/dist/development/core/cart/state.mjs.map +1 -1
  231. package/dist/development/core/collection/collection.d.mts +52 -53
  232. package/dist/development/core/collection/collection.mjs +5 -3
  233. package/dist/development/core/collection/collection.mjs.map +1 -1
  234. package/dist/development/core/collection/reconciler.d.mts +23 -16
  235. package/dist/development/core/collection/reconciler.mjs +8 -4
  236. package/dist/development/core/collection/reconciler.mjs.map +1 -1
  237. package/dist/development/core/collection/state.d.mts +31 -26
  238. package/dist/development/core/collection/state.mjs +4 -3
  239. package/dist/development/core/collection/state.mjs.map +1 -1
  240. package/dist/development/core/collection/url.d.mts +70 -29
  241. package/dist/development/core/collection/url.mjs +63 -25
  242. package/dist/development/core/collection/url.mjs.map +1 -1
  243. package/dist/development/core/constants.mjs.map +1 -1
  244. package/dist/development/core/logging/logging.d.mts +14 -8
  245. package/dist/development/core/logging/logging.mjs +15 -10
  246. package/dist/development/core/logging/logging.mjs.map +1 -1
  247. package/dist/development/core/logging/types.d.mts +28 -11
  248. package/dist/development/core/logging/types.mjs.map +1 -1
  249. package/dist/development/core/money/format.d.mts +6 -4
  250. package/dist/development/core/money/format.mjs +19 -9
  251. package/dist/development/core/money/format.mjs.map +1 -1
  252. package/dist/development/core/money/types.d.mts +41 -25
  253. package/dist/development/core/predictive-search/form.d.mts +37 -7
  254. package/dist/development/core/predictive-search/form.mjs +21 -3
  255. package/dist/development/core/predictive-search/form.mjs.map +1 -1
  256. package/dist/development/core/predictive-search/queries.d.mts +18 -30
  257. package/dist/development/core/predictive-search/queries.mjs +7 -3
  258. package/dist/development/core/predictive-search/queries.mjs.map +1 -1
  259. package/dist/development/core/predictive-search/search.d.mts +25 -27
  260. package/dist/development/core/predictive-search/search.mjs +20 -19
  261. package/dist/development/core/predictive-search/search.mjs.map +1 -1
  262. package/dist/development/core/predictive-search/server-handlers.d.mts +25 -26
  263. package/dist/development/core/predictive-search/server-handlers.mjs +4 -0
  264. package/dist/development/core/predictive-search/server-handlers.mjs.map +1 -1
  265. package/dist/development/core/predictive-search/store.d.mts +65 -24
  266. package/dist/development/core/predictive-search/store.mjs +12 -1
  267. package/dist/development/core/predictive-search/store.mjs.map +1 -1
  268. package/dist/development/core/predictive-search/url.d.mts +36 -37
  269. package/dist/development/core/predictive-search/url.mjs +14 -6
  270. package/dist/development/core/predictive-search/url.mjs.map +1 -1
  271. package/dist/development/core/product/form.d.mts +63 -16
  272. package/dist/development/core/product/form.mjs +8 -2
  273. package/dist/development/core/product/form.mjs.map +1 -1
  274. package/dist/development/core/product/options.d.mts +8 -10
  275. package/dist/development/core/product/options.mjs +8 -10
  276. package/dist/development/core/product/options.mjs.map +1 -1
  277. package/dist/development/core/product/product-form.d.mts +77 -29
  278. package/dist/development/core/product/product-form.mjs +32 -10
  279. package/dist/development/core/product/product-form.mjs.map +1 -1
  280. package/dist/development/core/product/state.d.mts +55 -32
  281. package/dist/development/core/product/url.d.mts +19 -13
  282. package/dist/development/core/product/url.mjs +10 -10
  283. package/dist/development/core/product/url.mjs.map +1 -1
  284. package/dist/development/core/request-context.d.mts +77 -19
  285. package/dist/development/core/request-context.mjs +5 -1
  286. package/dist/development/core/request-context.mjs.map +1 -1
  287. package/dist/development/core/request-routing/handle-shopify-redirects.d.mts +12 -4
  288. package/dist/development/core/request-routing/handle-shopify-redirects.mjs +9 -2
  289. package/dist/development/core/request-routing/handle-shopify-redirects.mjs.map +1 -1
  290. package/dist/development/core/request-routing/handle-shopify-routes.development.d.mts +9 -1
  291. package/dist/development/core/request-routing/handle-shopify-routes.development.mjs +7 -0
  292. package/dist/development/core/request-routing/handle-shopify-routes.development.mjs.map +1 -1
  293. package/dist/development/core/request-routing/handle-shopify-routes.mjs +7 -5
  294. package/dist/development/core/request-routing/handle-shopify-routes.mjs.map +1 -1
  295. package/dist/development/core/request-routing/interceptors/proxy.mjs +79 -43
  296. package/dist/development/core/request-routing/interceptors/proxy.mjs.map +1 -1
  297. package/dist/development/core/request-routing/interceptors/ucp-profile-proxy.mjs +69 -0
  298. package/dist/development/core/request-routing/interceptors/ucp-profile-proxy.mjs.map +1 -0
  299. package/dist/development/core/request-routing/interceptors/url-redirects.mjs +6 -1
  300. package/dist/development/core/request-routing/interceptors/url-redirects.mjs.map +1 -1
  301. package/dist/development/core/request-routing/interceptors/well-known.mjs +1 -1
  302. package/dist/development/core/request-routing/interceptors/well-known.mjs.map +1 -1
  303. package/dist/development/core/request-routing/registered-routes.d.mts +12 -0
  304. package/dist/development/core/request-routing/registered-routes.mjs +12 -0
  305. package/dist/development/core/request-routing/registered-routes.mjs.map +1 -1
  306. package/dist/development/core/request-routing/route-types.d.mts +43 -18
  307. package/dist/development/core/shop-pay/shop-pay.d.mts +71 -35
  308. package/dist/development/core/shop-pay/shop-pay.mjs +36 -10
  309. package/dist/development/core/shop-pay/shop-pay.mjs.map +1 -1
  310. package/dist/development/core/shopify-scripts/index.d.mts +15 -5
  311. package/dist/development/core/shopify-scripts/index.mjs +15 -5
  312. package/dist/development/core/shopify-scripts/index.mjs.map +1 -1
  313. package/dist/development/core/shopify-scripts/initialize.d.mts +12 -4
  314. package/dist/development/core/shopify-scripts/initialize.mjs +12 -4
  315. package/dist/development/core/shopify-scripts/initialize.mjs.map +1 -1
  316. package/dist/development/core/shopify-scripts/types.d.mts +58 -25
  317. package/dist/development/core/standard-routes/build.d.mts +11 -12
  318. package/dist/development/core/standard-routes/build.mjs +11 -12
  319. package/dist/development/core/standard-routes/build.mjs.map +1 -1
  320. package/dist/development/core/standard-routes/types.d.mts +30 -24
  321. package/dist/development/core/url.mjs +2 -1
  322. package/dist/development/core/url.mjs.map +1 -1
  323. package/dist/development/customer-account/session.d.mts +45 -54
  324. package/dist/development/customer-account/session.mjs +20 -4
  325. package/dist/development/customer-account/session.mjs.map +1 -1
  326. package/dist/development/globals.d.mts +20 -11
  327. package/dist/development/graphql/generated/storefront-api-types.d.mts +127 -1
  328. package/dist/development/graphql/graphql.d.mts +17 -16
  329. package/dist/development/graphql/graphql.mjs +7 -8
  330. package/dist/development/graphql/graphql.mjs.map +1 -1
  331. package/dist/development/graphql/type-resolver.d.mts +15 -8
  332. package/dist/development/react/cart.d.mts +78 -32
  333. package/dist/development/react/cart.mjs +56 -29
  334. package/dist/development/react/cart.mjs.map +1 -1
  335. package/dist/development/react/collection.d.mts +40 -22
  336. package/dist/development/react/collection.mjs +27 -7
  337. package/dist/development/react/collection.mjs.map +1 -1
  338. package/dist/development/react/predictive-search.d.mts +65 -50
  339. package/dist/development/react/predictive-search.mjs +23 -21
  340. package/dist/development/react/predictive-search.mjs.map +1 -1
  341. package/dist/development/react/product.d.mts +45 -16
  342. package/dist/development/react/product.mjs +13 -11
  343. package/dist/development/react/product.mjs.map +1 -1
  344. package/dist/development/react/shop-pay.d.mts +15 -0
  345. package/dist/development/react/shop-pay.mjs +9 -0
  346. package/dist/development/react/shop-pay.mjs.map +1 -1
  347. package/dist/development/react/shopify-scripts.d.mts +23 -2
  348. package/dist/development/react/shopify-scripts.mjs +9 -0
  349. package/dist/development/react/shopify-scripts.mjs.map +1 -1
  350. package/dist/development/vue/cart.d.mts +28 -23
  351. package/dist/development/vue/cart.mjs +42 -25
  352. package/dist/development/vue/cart.mjs.map +1 -1
  353. package/dist/development/vue/collection.d.mts +20 -12
  354. package/dist/development/vue/collection.mjs +15 -7
  355. package/dist/development/vue/collection.mjs.map +1 -1
  356. package/dist/development/vue/index.d.mts +2 -2
  357. package/dist/development/vue/index.mjs +2 -2
  358. package/dist/development/vue/predictive-search.d.mts +31 -36
  359. package/dist/development/vue/predictive-search.mjs +16 -22
  360. package/dist/development/vue/predictive-search.mjs.map +1 -1
  361. package/dist/development/vue/product.d.mts +16 -15
  362. package/dist/development/vue/product.mjs +11 -11
  363. package/dist/development/vue/product.mjs.map +1 -1
  364. package/dist/development/vue/shop-pay.d.mts +14 -17
  365. package/dist/development/vue/shop-pay.mjs +8 -0
  366. package/dist/development/vue/shop-pay.mjs.map +1 -1
  367. package/dist/development/vue/shopify-scripts.d.mts +11 -0
  368. package/dist/development/vue/shopify-scripts.mjs +6 -0
  369. package/dist/development/vue/shopify-scripts.mjs.map +1 -1
  370. package/dist/globals.d.mts +20 -11
  371. package/dist/graphql/generated/storefront-api-types.d.mts +127 -1
  372. package/dist/graphql/graphql.d.mts +17 -16
  373. package/dist/graphql/graphql.mjs +7 -8
  374. package/dist/graphql/graphql.mjs.map +1 -1
  375. package/dist/graphql/type-resolver.d.mts +15 -8
  376. package/dist/react/cart.d.mts +78 -32
  377. package/dist/react/cart.mjs +56 -29
  378. package/dist/react/cart.mjs.map +1 -1
  379. package/dist/react/collection.d.mts +40 -22
  380. package/dist/react/collection.mjs +27 -7
  381. package/dist/react/collection.mjs.map +1 -1
  382. package/dist/react/predictive-search.d.mts +65 -50
  383. package/dist/react/predictive-search.mjs +23 -21
  384. package/dist/react/predictive-search.mjs.map +1 -1
  385. package/dist/react/product.d.mts +45 -16
  386. package/dist/react/product.mjs +13 -11
  387. package/dist/react/product.mjs.map +1 -1
  388. package/dist/react/shop-pay.d.mts +15 -0
  389. package/dist/react/shop-pay.mjs +9 -0
  390. package/dist/react/shop-pay.mjs.map +1 -1
  391. package/dist/react/shopify-scripts.d.mts +23 -2
  392. package/dist/react/shopify-scripts.mjs +9 -0
  393. package/dist/react/shopify-scripts.mjs.map +1 -1
  394. package/dist/ts-plugin/index.cjs +6 -0
  395. package/dist/ts-plugin/index.cjs.map +1 -1
  396. package/dist/ts-plugin/index.d.cts +6 -0
  397. package/dist/vite/local-https.d.mts +51 -17
  398. package/dist/vite/local-https.mjs +26 -5
  399. package/dist/vite/local-https.mjs.map +1 -1
  400. package/dist/vue/cart.d.mts +28 -23
  401. package/dist/vue/cart.mjs +42 -25
  402. package/dist/vue/cart.mjs.map +1 -1
  403. package/dist/vue/collection.d.mts +20 -12
  404. package/dist/vue/collection.mjs +15 -7
  405. package/dist/vue/collection.mjs.map +1 -1
  406. package/dist/vue/index.d.mts +2 -2
  407. package/dist/vue/index.mjs +2 -2
  408. package/dist/vue/predictive-search.d.mts +31 -36
  409. package/dist/vue/predictive-search.mjs +16 -22
  410. package/dist/vue/predictive-search.mjs.map +1 -1
  411. package/dist/vue/product.d.mts +16 -15
  412. package/dist/vue/product.mjs +11 -11
  413. package/dist/vue/product.mjs.map +1 -1
  414. package/dist/vue/shop-pay.d.mts +14 -17
  415. package/dist/vue/shop-pay.mjs +8 -0
  416. package/dist/vue/shop-pay.mjs.map +1 -1
  417. package/dist/vue/shopify-scripts.d.mts +11 -0
  418. package/dist/vue/shopify-scripts.mjs +6 -0
  419. package/dist/vue/shopify-scripts.mjs.map +1 -1
  420. package/package.json +3 -1
  421. package/skills/hydrogen-analytics/SKILL.md +1 -1
  422. package/skills/hydrogen-cart-ui/SKILL.md +28 -2
  423. package/skills/hydrogen-collection-browser/SKILL.md +9 -1
  424. package/skills/hydrogen-collection-browser/references/nextjs.md +1 -1
  425. package/skills/hydrogen-collection-browser/references/nuxt.md +1 -1
  426. package/skills/hydrogen-collection-browser/references/react.md +151 -32
  427. package/skills/hydrogen-customer-account/SKILL.md +2 -0
  428. package/skills/hydrogen-request-handlers/SKILL.md +21 -11
  429. package/skills/hydrogen-setup/references/analytics.md +14 -8
  430. package/skills/hydrogen-setup/steps/1-inspect-app.md +2 -2
  431. package/skills/hydrogen-setup/steps/6-product-detail-page.md +21 -11
  432. package/skills/hydrogen-storefront-client/SKILL.md +1 -1
  433. package/skills/hydrogen-storefront-client/references/caching.md +2 -0
  434. package/skills/hydrogen-variant-form/SKILL.md +25 -7
  435. package/skills/hydrogen-variant-form/references/nextjs.md +39 -4
  436. package/skills/hydrogen-variant-form/references/nuxt.md +83 -42
  437. package/skills/hydrogen-variant-form/references/react.md +45 -9
@@ -1,107 +1,130 @@
1
1
  //#region src/core/product/state.d.ts
2
- /** Monetary amount in a specific currency, mirroring the Storefront API `MoneyV2` type. */
2
+ /** An amount and its currency, matching the Storefront API `MoneyV2` type. */
3
3
  interface Money {
4
4
  amount: string;
5
5
  currencyCode: string;
6
6
  }
7
- /** Minimum and maximum variant prices for a product, mirroring the Storefront API `ProductPriceRange` type. */
7
+ /** Lowest and highest variant prices for a product, matching the Storefront API product price range. */
8
8
  interface ProductPriceRange {
9
+ /** Lowest variant price. */
9
10
  minVariantPrice: Money;
10
- /** Optional so queries that only select `minVariantPrice` still type-check. */
11
+ /** Highest variant price. Leave the field out of queries that need only the lowest price. */
11
12
  maxVariantPrice?: Money;
12
13
  }
13
- /** A single selected product option, e.g. `{ name: "Color", value: "Red" }`. */
14
+ /** A single selected product option, such as `{ name: "Color", value: "Red" }`. */
14
15
  interface SelectedOption {
16
+ /** Option name, such as "Color". */
15
17
  name: string;
18
+ /** Value chosen for the option, such as "Red". */
16
19
  value: string;
17
20
  }
18
21
  /**
19
- * Minimum variant shape that the product form system requires.
22
+ * Variant fields that the product form needs from your Storefront API query.
20
23
  *
21
- * Consumers typically pass a wider type from their Storefront API query —
22
- * `ProductVariantInput` declares only the fields the form logic reads.
24
+ * Pass your full query type, which can include more fields.
23
25
  */
24
26
  interface ProductVariantInput {
27
+ /** Variant ID that the product form adds to the cart. */
25
28
  id: string;
29
+ /** Variant title. The cart shows the title on an added line before the server responds. */
26
30
  title: string;
31
+ /** Whether the customer can buy the variant. Option value availability falls back to this field when your query doesn't select the encoded variant availability. */
27
32
  availableForSale: boolean;
33
+ /** Option names and values that make up the variant. */
28
34
  selectedOptions: SelectedOption[];
35
+ /** Variant price. The cart shows the price on an added line before the server responds. */
29
36
  price: Money;
30
- /** Merchant-set compare-at price, typically shown struck-through when higher than `price`. `null` when unset; `undefined` when not queried. */
37
+ /** Compare-at price, or `null` when the variant has none. */
31
38
  compareAtPrice?: Money | null;
39
+ /** Variant image. The cart shows the image on an added line before the server responds. */
32
40
  image?: unknown;
41
+ /** Handle and title of the variant's product. A handle that differs from the current product marks a variant of another product in a combined listing. */
33
42
  product?: {
34
43
  handle: string;
35
44
  title?: string | null;
36
45
  } | null;
46
+ /** Variant SKU. */
37
47
  sku?: string | null;
38
48
  }
39
- /** A product option (e.g. "Size" or "Color") and its available values. */
49
+ /** A product option, such as "Size" or "Color", and its values. */
40
50
  interface ProductOptionInput<TVariant extends ProductVariantInput = ProductVariantInput> {
51
+ /** Option name, such as "Size" or "Color". */
41
52
  name: string;
53
+ /** Option values in the order that the Storefront API returns them. Keep that order, because the encoded variant fields refer to values by position. */
42
54
  optionValues: Array<ProductOptionValueInput<TVariant>>;
43
55
  }
44
- /** A single option value (e.g. "Small", "Red") within a {@link ProductOptionInput}. */
56
+ /** A single option value, such as "Small" or "Red", within a product option. */
45
57
  interface ProductOptionValueInput<TVariant extends ProductVariantInput = ProductVariantInput> {
58
+ /** Option value name, such as "Small" or "Red". */
46
59
  name: string;
47
- /** The variant combining this value with the lowest-position values of every other option (SFAPI). Strongly recommended: it seeds the variant cache used for `exists`/`selectedOptions` and combined-listing detection. */
60
+ /** Variant that combines this value with the lowest-position values of every other option. Include this field in your query. The store uses the variant to resolve option values and to find values that belong to other products in a combined listing. */
48
61
  firstSelectableVariant?: TVariant | null;
62
+ /** Swatch data from your query. The option value state returns the swatch unchanged. */
49
63
  swatch?: unknown;
50
64
  }
51
65
  /**
52
- * Minimum product shape that `createProductFormStore` requires.
66
+ * Product fields that the product form store needs from your Storefront API query.
53
67
  *
54
- * Fields mirror the Storefront API `Product` object. Consumers typically
55
- * pass a wider query result — this interface declares only what the
56
- * product form logic reads.
68
+ * The fields match the Storefront API product object. Pass your full query type, which can include more fields.
57
69
  */
58
70
  interface ProductInput<TVariant extends ProductVariantInput = ProductVariantInput> {
71
+ /** Product ID. The product provider reloads the product into the store when this ID or the selected variant's ID changes. */
59
72
  id: string;
73
+ /** Product title. */
60
74
  title: string;
75
+ /** Product handle for option value links. Variants with a different handle belong to other products in a combined listing. */
61
76
  handle: string;
77
+ /** Product vendor. */
62
78
  vendor?: string | null;
79
+ /** Lowest and highest variant prices. Show the range until the selection resolves to a variant. */
63
80
  priceRange?: ProductPriceRange;
64
- /** When `true`, the product cannot be added to cart without a selling plan. */
81
+ /** When `true`, the customer can't add the product to the cart without a selling plan. */
65
82
  requiresSellingPlan?: boolean | null;
66
- /** Encoded representation of which option-value combinations map to real variants. Consumers should treat this as opaque. */
83
+ /** Encoded list of the option value combinations that exist as variants. Pass the field unchanged from your query. */
67
84
  encodedVariantExistence?: string | null;
68
- /** Encoded representation of which existing variants are currently available for sale. Consumers should treat this as opaque. */
85
+ /** Encoded list of the variants that are available for sale. Pass the field unchanged from your query. */
69
86
  encodedVariantAvailability?: string | null;
87
+ /** Product options and their values. The option state keeps this order. */
70
88
  options: ProductOptionInput<TVariant>[];
71
- /** SFAPI `selectedOrFirstAvailableVariant`: the variant matching the query's `selectedOptions` argument, else the first available variant, else the first variant (which may be unavailable). */
89
+ /** The variant that matches the query's `selectedOptions` argument, or else the first available variant, or else the first variant, which can be unavailable. The store starts with this variant selected. */
72
90
  selectedOrFirstAvailableVariant: TVariant | null;
73
- /** Variants adjacent to the selected variant — used to resolve option values without a full variant list. */
91
+ /** Variants that differ from the selected variant by one option value. The store resolves option values from these variants, and you don't need to query every variant. */
74
92
  adjacentVariants: TVariant[];
75
93
  }
76
- /** Extracts the concrete variant type from a {@link ProductInput} subtype. */
94
+ /** Variant type from your product query type. */
77
95
  type ProductVariantFrom<TProduct extends ProductInput> = TProduct extends ProductInput<infer TVariant> ? TVariant : ProductVariantInput;
78
- /** Extracts the concrete option-value type from a {@link ProductInput} subtype. */
96
+ /** Option value type from your product query type. */
79
97
  type ProductOptionValueFrom<TProduct extends ProductInput> = TProduct["options"][number]["optionValues"][number];
80
98
  /**
81
- * Computed state for a single option value (e.g. "Red" under "Color").
99
+ * State of one option value, such as "Red" under "Color", for the current selection.
82
100
  *
83
- * Derived each time the selection changes — fields reflect the current
84
- * selection context, not static product data.
101
+ * Render the value's control and link from this state.
85
102
  */
86
103
  interface VariantOptionValueState<TVariant extends ProductVariantInput = ProductVariantInput, TOptionValue extends ProductOptionValueInput = ProductOptionValueInput> {
104
+ /** Option value name, such as "Red". */
87
105
  name: string;
106
+ /** Swatch data from the option value in your query. */
88
107
  swatch?: TOptionValue["swatch"];
89
- /** Whether this value is the current selection for its option. */
108
+ /** Whether the customer has selected the value. */
90
109
  selected: boolean;
91
- /** Whether a variant exists for the target selection. Falls back to `true` when `encodedVariantExistence` wasn't queried. */
110
+ /** Whether a variant exists for the selection that the value targets. Defaults to `true` when your query doesn't select the encoded variant existence field. */
92
111
  exists: boolean;
93
- /** Whether the target selection is available for sale. Uses `encodedVariantAvailability` when queried, otherwise the loaded variant's `availableForSale` (`false` if not loaded). */
112
+ /** Whether the variant for the targeted selection is available for sale. Without the encoded variant availability in your query, the value comes from the loaded variant, or reads `false` when your query didn't load that variant. */
94
113
  available: boolean;
95
- /** The loaded variant for the target selection, or `null` if the selection is partial or that variant wasn't part of the query result. */
114
+ /** Variant for the targeted selection, or `null` when the selection is partial or your query didn't load that variant. */
96
115
  variant: TVariant | null;
97
- /** The selection this value targets. Use for link building; `selectOption()` is the source of truth for the resulting selection. */
116
+ /** Selection that the value targets. Build the option link from this selection. Selecting the value can produce a different selection. */
98
117
  selectedOptions: SelectedOption[];
99
- /** Product handle, useful for building navigation links to combined-listing child products. */
118
+ /** Product handle for the option link. A value from another product in a combined listing uses that product's handle. Selected values use the current product's handle. */
100
119
  handle: string;
101
120
  }
102
- /** Computed state for a product option (e.g. "Color"), grouping its {@link VariantOptionValueState} entries. */
121
+ /**
122
+ * State of one product option, such as "Color", and its values.
123
+ */
103
124
  interface VariantOptionState<TVariant extends ProductVariantInput = ProductVariantInput, TOptionValue extends ProductOptionValueInput = ProductOptionValueInput> {
125
+ /** Option name, such as "Color". */
104
126
  name: string;
127
+ /** State of each value, in the product's value order. */
105
128
  values: VariantOptionValueState<TVariant, TOptionValue>[];
106
129
  }
107
130
  //#endregion
@@ -2,25 +2,24 @@ import { SelectedOption } from "./state.mjs";
2
2
 
3
3
  //#region src/core/product/url.d.ts
4
4
  /**
5
- * Controls how variant selection is encoded in URL search params.
5
+ * How a product URL encodes the customer's variant selection.
6
6
  *
7
- * - `"options"` — one param per option, e.g. `?Color=Red&Size=M`.
8
- * - `"variant"` — a single `?variant=<numeric id>` param (Liquid parity).
7
+ * - `options`: one param per option, such as `?Color=Red&Size=M`.
8
+ * - `variant`: a single `?variant=<numeric id>` param that matches Liquid storefront URLs.
9
9
  */
10
10
  type ProductSelectionLinkStyle = "options" | "variant";
11
11
  /**
12
- * Builds the search params for a product-page link representing a variant selection.
12
+ * Builds the search params for a product URL that holds the customer's variant selection.
13
13
  *
14
- * Existing `base` params are preserved (`?ref=campaign` survives navigation), except the
15
- * reserved `variant` param and every param named in `optionNames` or `selectedOptions`,
16
- * which are always removed before the new selection is written. This keeps stale selection
17
- * state from leaking between navigations, including across combined-listing products.
14
+ * The function keeps unrelated params from the base params, such as `?ref=campaign`, and replaces any earlier selection. Pass the current product's option names to clear the current product's params when you link to another product in a combined listing.
18
15
  *
19
- * Two link styles:
20
- * - `"options"` (default): one param per selected option, e.g. `?Color=Red&Size=M`.
21
- * - `"variant"`: a single `?variant=<numeric id>` param (Liquid parity), for shareable
22
- * links. Falls back to option params when `variant` is absent — a partial selection has
23
- * no matching variant, so a variant link is not constructible.
16
+ * The default options style writes one param per option. The variant style writes a single numeric variant ID param for shareable links.
17
+ * The variant style falls back to option params when you pass no variant or when the variant ID isn't a numeric product variant GID.
18
+ * The `handleShopifyRoutes` handler redirects variant links to option param URLs.
19
+ *
20
+ * @param input The selected options, the current product's option names, and an optional variant, link style, and base params.
21
+ *
22
+ * @returns New search params that hold the kept base params and the selection.
24
23
  *
25
24
  * @example
26
25
  * ```ts
@@ -32,6 +31,7 @@ type ProductSelectionLinkStyle = "options" | "variant";
32
31
  * });
33
32
  * const url = `/products/${handle}${searchParams.size ? `?${searchParams}` : ""}`;
34
33
  * ```
34
+ * @publicDocs
35
35
  */
36
36
  declare function buildProductSelectionSearchParams({
37
37
  style,
@@ -40,11 +40,17 @@ declare function buildProductSelectionSearchParams({
40
40
  optionNames,
41
41
  base
42
42
  }: {
43
+ /**
44
+ * Link style for the selection. Defaults to `"options"`.
45
+ */
43
46
  style?: ProductSelectionLinkStyle;
44
47
  selectedOptions: readonly SelectedOption[];
45
48
  variant?: {
46
49
  id: string;
47
50
  } | null;
51
+ /**
52
+ * Option names of the current product. The function clears the param for each name.
53
+ */
48
54
  optionNames: readonly string[];
49
55
  base?: URLSearchParams;
50
56
  }): URLSearchParams;
@@ -26,18 +26,17 @@ function getVariantSearchParamValue(variantId) {
26
26
  return /^\d+$/.test(numericId) ? numericId : null;
27
27
  }
28
28
  /**
29
- * Builds the search params for a product-page link representing a variant selection.
29
+ * Builds the search params for a product URL that holds the customer's variant selection.
30
30
  *
31
- * Existing `base` params are preserved (`?ref=campaign` survives navigation), except the
32
- * reserved `variant` param and every param named in `optionNames` or `selectedOptions`,
33
- * which are always removed before the new selection is written. This keeps stale selection
34
- * state from leaking between navigations, including across combined-listing products.
31
+ * The function keeps unrelated params from the base params, such as `?ref=campaign`, and replaces any earlier selection. Pass the current product's option names to clear the current product's params when you link to another product in a combined listing.
35
32
  *
36
- * Two link styles:
37
- * - `"options"` (default): one param per selected option, e.g. `?Color=Red&Size=M`.
38
- * - `"variant"`: a single `?variant=<numeric id>` param (Liquid parity), for shareable
39
- * links. Falls back to option params when `variant` is absent — a partial selection has
40
- * no matching variant, so a variant link is not constructible.
33
+ * The default options style writes one param per option. The variant style writes a single numeric variant ID param for shareable links.
34
+ * The variant style falls back to option params when you pass no variant or when the variant ID isn't a numeric product variant GID.
35
+ * The `handleShopifyRoutes` handler redirects variant links to option param URLs.
36
+ *
37
+ * @param input The selected options, the current product's option names, and an optional variant, link style, and base params.
38
+ *
39
+ * @returns New search params that hold the kept base params and the selection.
41
40
  *
42
41
  * @example
43
42
  * ```ts
@@ -49,6 +48,7 @@ function getVariantSearchParamValue(variantId) {
49
48
  * });
50
49
  * const url = `/products/${handle}${searchParams.size ? `?${searchParams}` : ""}`;
51
50
  * ```
51
+ * @publicDocs
52
52
  */
53
53
  function buildProductSelectionSearchParams({ style = "options", selectedOptions, variant, optionNames, base }) {
54
54
  const searchParams = new URLSearchParams(base);
@@ -1 +1 @@
1
- {"version":3,"file":"url.mjs","names":[],"sources":["../../../src/core/product/url.ts"],"sourcesContent":["import type { SelectedOption } from \"./state\";\n\nconst VARIANT_GID_PREFIX = \"gid://shopify/ProductVariant/\";\n\n/**\n * Reserved product-page search param carrying a numeric variant id (`?variant=41820371452004`),\n * matching Liquid storefront URLs. Never treated as a product option name.\n */\nexport const VARIANT_SEARCH_PARAM = \"variant\";\n\n/**\n * Converts a `?variant=` search param value into a variant id (gid).\n *\n * Anything that is not a plain positive integer of a plausible length is treated as absent\n * (`null`) so malformed links degrade to the default variant instead of producing invalid\n * API lookups or log spam.\n */\nexport function parseVariantSearchParam(value: string | null): string | null {\n if (!value || !/^\\d{1,30}$/.test(value)) return null;\n return `${VARIANT_GID_PREFIX}${value}`;\n}\n\n/**\n * Extracts the numeric `?variant=` search param value from a variant id (gid).\n * Returns `null` for ids that are not canonical `gid://shopify/ProductVariant/<numeric>` ids.\n */\nexport function getVariantSearchParamValue(variantId: string): string | null {\n if (!variantId.startsWith(VARIANT_GID_PREFIX)) return null;\n\n const numericId = variantId.slice(VARIANT_GID_PREFIX.length);\n return /^\\d+$/.test(numericId) ? numericId : null;\n}\n\n/**\n * Controls how variant selection is encoded in URL search params.\n *\n * - `\"options\"` — one param per option, e.g. `?Color=Red&Size=M`.\n * - `\"variant\"` — a single `?variant=<numeric id>` param (Liquid parity).\n */\nexport type ProductSelectionLinkStyle = \"options\" | \"variant\";\n\n/**\n * Builds the search params for a product-page link representing a variant selection.\n *\n * Existing `base` params are preserved (`?ref=campaign` survives navigation), except the\n * reserved `variant` param and every param named in `optionNames` or `selectedOptions`,\n * which are always removed before the new selection is written. This keeps stale selection\n * state from leaking between navigations, including across combined-listing products.\n *\n * Two link styles:\n * - `\"options\"` (default): one param per selected option, e.g. `?Color=Red&Size=M`.\n * - `\"variant\"`: a single `?variant=<numeric id>` param (Liquid parity), for shareable\n * links. Falls back to option params when `variant` is absent — a partial selection has\n * no matching variant, so a variant link is not constructible.\n *\n * @example\n * ```ts\n * const searchParams = buildProductSelectionSearchParams({\n * selectedOptions: result.selectedOptions,\n * variant: result.selectedVariant,\n * optionNames: product.options.map((option) => option.name),\n * base: new URLSearchParams(location.search),\n * });\n * const url = `/products/${handle}${searchParams.size ? `?${searchParams}` : \"\"}`;\n * ```\n */\nexport function buildProductSelectionSearchParams({\n style = \"options\",\n selectedOptions,\n variant,\n optionNames,\n base,\n}: {\n style?: ProductSelectionLinkStyle;\n selectedOptions: readonly SelectedOption[];\n variant?: { id: string } | null;\n optionNames: readonly string[];\n base?: URLSearchParams;\n}): URLSearchParams {\n const searchParams = new URLSearchParams(base);\n\n searchParams.delete(VARIANT_SEARCH_PARAM);\n for (const name of optionNames) searchParams.delete(name);\n for (const option of selectedOptions) searchParams.delete(option.name);\n\n const variantSearchParamValue =\n style === \"variant\" && variant ? getVariantSearchParamValue(variant.id) : null;\n\n if (variantSearchParamValue) {\n searchParams.set(VARIANT_SEARCH_PARAM, variantSearchParamValue);\n return searchParams;\n }\n\n for (const option of selectedOptions) {\n searchParams.set(option.name, option.value);\n }\n\n return searchParams;\n}\n"],"mappings":";AAEA,MAAM,qBAAqB;;;;;AAM3B,MAAa,uBAAuB;;;;;;;;AASpC,SAAgB,wBAAwB,OAAqC;AAC3E,KAAI,CAAC,SAAS,CAAC,aAAa,KAAK,MAAM,CAAE,QAAO;AAChD,QAAO,GAAG,qBAAqB;;;;;;AAOjC,SAAgB,2BAA2B,WAAkC;AAC3E,KAAI,CAAC,UAAU,WAAW,mBAAmB,CAAE,QAAO;CAEtD,MAAM,YAAY,UAAU,MAAM,GAA0B;AAC5D,QAAO,QAAQ,KAAK,UAAU,GAAG,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoC/C,SAAgB,kCAAkC,EAChD,QAAQ,WACR,iBACA,SACA,aACA,QAOkB;CAClB,MAAM,eAAe,IAAI,gBAAgB,KAAK;AAE9C,cAAa,OAAO,qBAAqB;AACzC,MAAK,MAAM,QAAQ,YAAa,cAAa,OAAO,KAAK;AACzD,MAAK,MAAM,UAAU,gBAAiB,cAAa,OAAO,OAAO,KAAK;CAEtE,MAAM,0BACJ,UAAU,aAAa,UAAU,2BAA2B,QAAQ,GAAG,GAAG;AAE5E,KAAI,yBAAyB;AAC3B,eAAa,IAAI,sBAAsB,wBAAwB;AAC/D,SAAO;;AAGT,MAAK,MAAM,UAAU,gBACnB,cAAa,IAAI,OAAO,MAAM,OAAO,MAAM;AAG7C,QAAO"}
1
+ {"version":3,"file":"url.mjs","names":[],"sources":["../../../src/core/product/url.ts"],"sourcesContent":["import type { SelectedOption } from \"./state\";\n\nconst VARIANT_GID_PREFIX = \"gid://shopify/ProductVariant/\";\n\n/**\n * Reserved product-page search param carrying a numeric variant id (`?variant=41820371452004`),\n * matching Liquid storefront URLs. Never treated as a product option name.\n */\nexport const VARIANT_SEARCH_PARAM = \"variant\";\n\n/**\n * Converts a `?variant=` search param value into a variant id (gid).\n *\n * Anything that is not a plain positive integer of a plausible length is treated as absent\n * (`null`) so malformed links degrade to the default variant instead of producing invalid\n * API lookups or log spam.\n */\nexport function parseVariantSearchParam(value: string | null): string | null {\n if (!value || !/^\\d{1,30}$/.test(value)) return null;\n return `${VARIANT_GID_PREFIX}${value}`;\n}\n\n/**\n * Extracts the numeric `?variant=` search param value from a variant id (gid).\n * Returns `null` for ids that are not canonical `gid://shopify/ProductVariant/<numeric>` ids.\n */\nexport function getVariantSearchParamValue(variantId: string): string | null {\n if (!variantId.startsWith(VARIANT_GID_PREFIX)) return null;\n\n const numericId = variantId.slice(VARIANT_GID_PREFIX.length);\n return /^\\d+$/.test(numericId) ? numericId : null;\n}\n\n/**\n * How a product URL encodes the customer's variant selection.\n *\n * - `options`: one param per option, such as `?Color=Red&Size=M`.\n * - `variant`: a single `?variant=<numeric id>` param that matches Liquid storefront URLs.\n */\nexport type ProductSelectionLinkStyle = \"options\" | \"variant\";\n\n/**\n * Builds the search params for a product URL that holds the customer's variant selection.\n *\n * The function keeps unrelated params from the base params, such as `?ref=campaign`, and replaces any earlier selection. Pass the current product's option names to clear the current product's params when you link to another product in a combined listing.\n *\n * The default options style writes one param per option. The variant style writes a single numeric variant ID param for shareable links.\n * The variant style falls back to option params when you pass no variant or when the variant ID isn't a numeric product variant GID.\n * The `handleShopifyRoutes` handler redirects variant links to option param URLs.\n *\n * @param input The selected options, the current product's option names, and an optional variant, link style, and base params.\n *\n * @returns New search params that hold the kept base params and the selection.\n *\n * @example\n * ```ts\n * const searchParams = buildProductSelectionSearchParams({\n * selectedOptions: result.selectedOptions,\n * variant: result.selectedVariant,\n * optionNames: product.options.map((option) => option.name),\n * base: new URLSearchParams(location.search),\n * });\n * const url = `/products/${handle}${searchParams.size ? `?${searchParams}` : \"\"}`;\n * ```\n * @publicDocs\n */\nexport function buildProductSelectionSearchParams({\n style = \"options\",\n selectedOptions,\n variant,\n optionNames,\n base,\n}: {\n /**\n * Link style for the selection. Defaults to `\"options\"`.\n */\n style?: ProductSelectionLinkStyle;\n selectedOptions: readonly SelectedOption[];\n variant?: { id: string } | null;\n /**\n * Option names of the current product. The function clears the param for each name.\n */\n optionNames: readonly string[];\n base?: URLSearchParams;\n}): URLSearchParams {\n const searchParams = new URLSearchParams(base);\n\n searchParams.delete(VARIANT_SEARCH_PARAM);\n for (const name of optionNames) searchParams.delete(name);\n for (const option of selectedOptions) searchParams.delete(option.name);\n\n const variantSearchParamValue =\n style === \"variant\" && variant ? getVariantSearchParamValue(variant.id) : null;\n\n if (variantSearchParamValue) {\n searchParams.set(VARIANT_SEARCH_PARAM, variantSearchParamValue);\n return searchParams;\n }\n\n for (const option of selectedOptions) {\n searchParams.set(option.name, option.value);\n }\n\n return searchParams;\n}\n"],"mappings":";AAEA,MAAM,qBAAqB;;;;;AAM3B,MAAa,uBAAuB;;;;;;;;AASpC,SAAgB,wBAAwB,OAAqC;AAC3E,KAAI,CAAC,SAAS,CAAC,aAAa,KAAK,MAAM,CAAE,QAAO;AAChD,QAAO,GAAG,qBAAqB;;;;;;AAOjC,SAAgB,2BAA2B,WAAkC;AAC3E,KAAI,CAAC,UAAU,WAAW,mBAAmB,CAAE,QAAO;CAEtD,MAAM,YAAY,UAAU,MAAM,GAA0B;AAC5D,QAAO,QAAQ,KAAK,UAAU,GAAG,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoC/C,SAAgB,kCAAkC,EAChD,QAAQ,WACR,iBACA,SACA,aACA,QAakB;CAClB,MAAM,eAAe,IAAI,gBAAgB,KAAK;AAE9C,cAAa,OAAO,qBAAqB;AACzC,MAAK,MAAM,QAAQ,YAAa,cAAa,OAAO,KAAK;AACzD,MAAK,MAAM,UAAU,gBAAiB,cAAa,OAAO,OAAO,KAAK;CAEtE,MAAM,0BACJ,UAAU,aAAa,UAAU,2BAA2B,QAAQ,GAAG,GAAG;AAE5E,KAAI,yBAAyB;AAC3B,eAAa,IAAI,sBAAsB,wBAAwB;AAC/D,SAAO;;AAGT,MAAK,MAAM,UAAU,gBACnB,cAAa,IAAI,OAAO,MAAM,OAAO,MAAM;AAG7C,QAAO"}
@@ -2,19 +2,31 @@ import { CountryCode, LanguageCode } from "../graphql/generated/customer-account
2
2
  import { CountryCode as CountryCode$1, LanguageCode as LanguageCode$1 } from "../graphql/generated/storefront-api-types.mjs";
3
3
 
4
4
  //#region src/core/request-context.d.ts
5
+ /** The incoming request's headers, plus its method, abort signal, and URL when the framework provides them. */
5
6
  type StorefrontRequest = Pick<Request, "headers"> & Partial<Pick<Request, "method" | "signal" | "url">>;
7
+ /** A language code that both the Storefront API and the Customer Account API accept. */
6
8
  type ShopifyLanguageCode = Extract<LanguageCode$1, LanguageCode>;
9
+ /** A country code that both the Storefront API and the Customer Account API accept. */
7
10
  type ShopifyCountryCode = Extract<CountryCode$1, CountryCode>;
11
+ /** The language, country, and optional localized path prefix for a request. */
8
12
  type I18nConfig = {
9
- language: ShopifyLanguageCode;
10
- country: ShopifyCountryCode; /** Optional app route prefix for localized paths, for example "/es-es". */
13
+ /** The request's language. */language: ShopifyLanguageCode; /** The request's country. */
14
+ country: ShopifyCountryCode;
15
+ /**
16
+ * The URL prefix for localized routes, such as `/es-es`. Hydrogen adds the prefix to standard route redirects and product variant URLs.
17
+ */
11
18
  pathPrefix?: string;
12
19
  };
20
+ /** The request's locale, with a path prefix that starts with `/` or is an empty string. */
13
21
  type NormalizedI18nConfig<I18n extends I18nConfig = I18nConfig> = Omit<I18n, "pathPrefix"> & {
14
22
  pathPrefix: string;
15
23
  };
24
+ /** The incoming request and its locale. */
16
25
  type ShopifyRequestContextInputBase<I18n extends I18nConfig = I18nConfig> = {
17
- request: StorefrontRequest;
26
+ /**
27
+ * The incoming request from your framework. When the framework exposes no request, such as during prerendering, pass `{ headers: new Headers() }`.
28
+ */
29
+ request: StorefrontRequest; /** The request's language, country, and optional localized path prefix. */
18
30
  i18n: I18n;
19
31
  };
20
32
  type ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> = ShopifyRequestContextInputBase<I18n> & {
@@ -23,54 +35,100 @@ type ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> = ShopifyR
23
35
  type ShopifyRequestContextWithBuyerIpInput<I18n extends I18nConfig = I18nConfig> = ShopifyRequestContextInputBase<I18n> & {
24
36
  buyerIp: string;
25
37
  };
38
+ /** The fields and methods on every request context. */
26
39
  type ShopifyRequestContextBase = {
27
40
  /**
28
- * Compile-time brand so callers use createShopifyRequestContext().
41
+ * A type-only marker. Create request contexts only with `createShopifyRequestContext`.
42
+ * @internal
43
+ */
44
+ readonly __hydrogenShopifyRequestContextBrand: never;
45
+ /**
46
+ * The incoming request's `Cookie` header. Hydrogen forwards the header to the Storefront API.
47
+ * @internal
48
+ */
49
+ cookie?: string;
50
+ /**
51
+ * The customer's trusted IP address. Private Storefront API clients require it.
52
+ * @internal
53
+ */
54
+ readonly buyerIp?: string;
55
+ /**
56
+ * The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one.
57
+ * @internal
58
+ */
59
+ requestGroupId: string;
60
+ /**
61
+ * The incoming request's abort signal. Hydrogen's API clients stop their requests when the signal aborts.
62
+ * @internal
63
+ */
64
+ signal?: AbortSignal;
65
+ /**
66
+ * The incoming request's URL.
67
+ * @internal
68
+ */
69
+ url?: string;
70
+ /**
71
+ * The origin of the incoming request's URL. Hydrogen forwards the origin to the Storefront API.
29
72
  * @internal
30
73
  */
31
- readonly __hydrogenShopifyRequestContextBrand: never; /** @internal */
32
- cookie?: string; /** @internal */
33
- readonly buyerIp?: string; /** @internal */
34
- requestGroupId: string; /** @internal */
35
- signal?: AbortSignal; /** @internal */
36
- url?: string; /** @internal */
37
74
  storefrontOrigin?: string;
38
75
  /**
39
- * Apply request-scoped headers required by every Shopify storefront subrequest.
76
+ * Adds the Hydrogen SDK headers and the customer's cookie, privacy, and origin headers to a
77
+ * Storefront API request. The Storefront API client and the API proxies call the method for you.
40
78
  * @internal
41
79
  */
42
80
  applyStorefrontRequestHeaders(headers: Headers): void;
43
81
  /**
44
- * Capture cookies from the first fresh storefront response that sets them for replay.
82
+ * Saves the cookies from the first Storefront API response that sets cookies. The Storefront API
83
+ * client calls the method for you, and `applyResponseHeaders()` can add the cookies to your response.
45
84
  * @internal
46
85
  */
47
86
  captureSubrequestHeaders(headers: Headers): void;
48
87
  /**
49
- * Consume storefront proxy cookies for gated replay onto the final app response.
88
+ * Saves the cookies from a proxied Storefront API response and removes the `set-cookie` header
89
+ * from that response. The API proxies in `handleShopifyRoutes` call the method for you.
50
90
  * @internal
51
91
  */
52
92
  consumeStorefrontResponseHeaders(headers: Headers): void;
53
93
  /**
54
- * Mark the final app response as influenced by private customer state.
94
+ * Marks the final response as private and uncacheable because the response holds customer data.
95
+ * The Customer Account API client and the customer session call the method for you.
55
96
  * @internal
56
97
  */
57
98
  markResponseAsPersonalized(reason: string): void;
58
99
  /**
59
- * Mark this request as a sanctioned session-establishing endpoint (like the
60
- * consent flow), permitting Shopify state to return even on a cold session.
100
+ * Lets the final response set Shopify cookies before the customer has a Shopify session, and
101
+ * marks the response as private and uncacheable. The UCP MCP proxy calls the method for you.
61
102
  * @internal
62
103
  */
63
- markResponseAsSessionEstablishing(reason: string): void;
64
- i18n: NormalizedI18nConfig; /** Return incoming request headers plus request lifecycle headers for proxy/origin handoff. */
65
- getForwardedRequestHeaders(): Headers; /** Apply important response headers for the correct functioning of Hydrogen storefronts. */
104
+ markResponseAsSessionEstablishing(reason: string): void; /** The request's language, country, and path prefix. The path prefix starts with `/` and has no trailing slash, or is an empty string when you don't set one. */
105
+ i18n: NormalizedI18nConfig; /** Returns the incoming request's headers with Shopify's request headers added. Pass the headers when you forward the request to a proxy or another origin. */
106
+ getForwardedRequestHeaders(): Headers;
107
+ /**
108
+ * Adds the headers that a Hydrogen storefront needs to the final response. Call the method on each response that your app builds. Responses from `handleShopifyRoutes` and `handleShopifyRedirects` already carry these headers.
109
+ *
110
+ * Append your committed session headers before you call the method. The method makes any response that sets a cookie private and uncacheable. The method also sets the `powered-by` header, makes responses that hold customer data private and uncacheable, and adds eligible Shopify cookies only to non-document responses for methods other than `GET` and `HEAD`.
111
+ */
66
112
  applyResponseHeaders(headers: Headers): void;
67
113
  };
114
+ /** The context for one request. Pass the context to Hydrogen's API clients and route handlers for that request. */
68
115
  type ShopifyRequestContext<I18n extends I18nConfig = I18nConfig> = ShopifyRequestContextBase & {
69
116
  i18n: NormalizedI18nConfig<I18n>;
70
117
  };
118
+ /** A request context that includes the customer's IP address. Private Storefront API clients require this context. */
71
119
  type ShopifyRequestContextWithBuyerIp<I18n extends I18nConfig = I18nConfig> = ShopifyRequestContext<I18n> & {
72
120
  readonly buyerIp: string;
73
121
  };
122
+ /**
123
+ * Creates the context for one incoming request. Pass the same context to the Storefront API
124
+ * client, the Customer Account API client, and `handleShopifyRoutes`. The context forwards the
125
+ * customer's cookies, locale, and privacy signal to Shopify, and adds Shopify's cookies and cache
126
+ * headers to your response.
127
+ *
128
+ * Call `applyResponseHeaders()` on each response that your app builds. Pass `buyerIp` with the customer's trusted IP address when you use a private Storefront API client. The function throws when `i18n` lacks a country or language, and when `buyerIp` is an empty string.
129
+ *
130
+ * @publicDocs
131
+ */
74
132
  declare function createShopifyRequestContext<const I18n extends I18nConfig>(input: ShopifyRequestContextWithBuyerIpInput<I18n>): ShopifyRequestContextWithBuyerIp<I18n>;
75
133
  declare function createShopifyRequestContext<const I18n extends I18nConfig>(input: ShopifyRequestContextInput<I18n>): ShopifyRequestContext<I18n>;
76
134
  //#endregion
@@ -4,6 +4,10 @@ import { CONSENT_MANAGEMENT_HEADER, HYDROGEN_VERSION_HEADER, REQUEST_GROUP_ID_HE
4
4
 
5
5
  //#region src/core/request-context.ts
6
6
  const SHOPIFY_ESSENTIAL_COOKIE = "_shopify_essential";
7
+ /**
8
+ * @param input The incoming request, its locale, and the customer's IP address for private Storefront API clients.
9
+ * @returns The context to pass to Hydrogen's API clients and route handlers for this request.
10
+ */
7
11
  function createShopifyRequestContext(input) {
8
12
  const { request } = input;
9
13
  if (!input.i18n?.country || !input.i18n?.language) throw new Error("i18n with country and language is required for Shopify request contexts.");
@@ -80,7 +84,7 @@ function applyStorefrontRequestHeaders(context, headers) {
80
84
  headers.set(SDK_VARIANT_HEADER, "hydrogen");
81
85
  headers.set(SDK_VARIANT_SOURCE_HEADER, "kit");
82
86
  headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);
83
- headers.set(HYDROGEN_VERSION_HEADER, "2026.10.0-preview.4");
87
+ headers.set(HYDROGEN_VERSION_HEADER, "2026.10.0");
84
88
  headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);
85
89
  if (context.cookie) headers.set("cookie", context.cookie);
86
90
  else headers.delete("cookie");
@@ -1 +1 @@
1
- {"version":3,"file":"request-context.mjs","names":[],"sources":["../../src/core/request-context.ts"],"sourcesContent":["import type {\n CountryCode as CustomerAccountCountryCode,\n LanguageCode as CustomerAccountLanguageCode,\n} from \"../graphql/generated/customer-account-api-types\";\nimport type {\n CountryCode as StorefrontCountryCode,\n LanguageCode as StorefrontLanguageCode,\n} from \"../graphql/generated/storefront-api-types\";\nimport { STOREFRONT_API_VERSION } from \"./constants\";\nimport {\n applyPrivateResponseCacheHeaders,\n CONSENT_MANAGEMENT_HEADER,\n HYDROGEN_VERSION_HEADER,\n REQUEST_GROUP_ID_HEADER,\n SDK_VARIANT_HEADER,\n SDK_VARIANT_SOURCE_HEADER,\n SDK_VERSION_HEADER,\n SEC_GPC_HEADER,\n SHOPIFY_STOREFRONT_ORIGIN_HEADER,\n STOREFRONT_URL_HEADER,\n} from \"./headers\";\nimport { normalizePathPrefix } from \"./standard-routes/path\";\n\nconst SHOPIFY_ESSENTIAL_COOKIE = \"_shopify_essential\";\n\ntype StorefrontRequest = Pick<Request, \"headers\"> &\n Partial<Pick<Request, \"method\" | \"signal\" | \"url\">>;\n\nexport type ShopifyLanguageCode = Extract<StorefrontLanguageCode, CustomerAccountLanguageCode>;\nexport type ShopifyCountryCode = Extract<StorefrontCountryCode, CustomerAccountCountryCode>;\n\nexport type I18nConfig = {\n language: ShopifyLanguageCode;\n country: ShopifyCountryCode;\n /** Optional app route prefix for localized paths, for example \"/es-es\". */\n pathPrefix?: string;\n};\n\ntype NormalizedI18nConfig<I18n extends I18nConfig = I18nConfig> = Omit<I18n, \"pathPrefix\"> & {\n pathPrefix: string;\n};\n\ntype ShopifyRequestContextInputBase<I18n extends I18nConfig = I18nConfig> = {\n request: StorefrontRequest;\n i18n: I18n;\n};\n\ntype ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp?: never };\n\ntype ShopifyRequestContextWithBuyerIpInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp: string };\n\ntype ShopifyRequestContextBase = {\n // -- Private fields --\n /**\n * Compile-time brand so callers use createShopifyRequestContext().\n * @internal\n */\n readonly __hydrogenShopifyRequestContextBrand: never;\n /** @internal */\n cookie?: string;\n /** @internal */\n readonly buyerIp?: string;\n /** @internal */\n requestGroupId: string;\n /** @internal */\n signal?: AbortSignal;\n /** @internal */\n url?: string;\n /** @internal */\n storefrontOrigin?: string;\n /**\n * Apply request-scoped headers required by every Shopify storefront subrequest.\n * @internal\n */\n applyStorefrontRequestHeaders(headers: Headers): void;\n /**\n * Capture cookies from the first fresh storefront response that sets them for replay.\n * @internal\n */\n captureSubrequestHeaders(headers: Headers): void;\n /**\n * Consume storefront proxy cookies for gated replay onto the final app response.\n * @internal\n */\n consumeStorefrontResponseHeaders(headers: Headers): void;\n /**\n * Mark the final app response as influenced by private customer state.\n * @internal\n */\n markResponseAsPersonalized(reason: string): void;\n /**\n * Mark this request as a sanctioned session-establishing endpoint (like the\n * consent flow), permitting Shopify state to return even on a cold session.\n * @internal\n */\n markResponseAsSessionEstablishing(reason: string): void;\n\n // -- Public fields --\n i18n: NormalizedI18nConfig;\n /** Return incoming request headers plus request lifecycle headers for proxy/origin handoff. */\n getForwardedRequestHeaders(): Headers;\n /** Apply important response headers for the correct functioning of Hydrogen storefronts. */\n applyResponseHeaders(headers: Headers): void;\n};\n\nexport type ShopifyRequestContext<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextBase & {\n i18n: NormalizedI18nConfig<I18n>;\n };\n\nexport type ShopifyRequestContextWithBuyerIp<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContext<I18n> & { readonly buyerIp: string };\n\ntype Context<I18n extends I18nConfig = I18nConfig> = {\n cookie?: string;\n globalPrivacyControl?: string;\n buyerIp?: string;\n requestGroupId: string;\n signal?: AbortSignal;\n url?: string;\n storefrontOrigin?: string;\n i18n: NormalizedI18nConfig<I18n>;\n documentRequest?: boolean;\n};\n\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextWithBuyerIpInput<I18n>,\n): ShopifyRequestContextWithBuyerIp<I18n>;\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInput<I18n>,\n): ShopifyRequestContext<I18n>;\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInputBase<I18n> & { buyerIp?: string },\n): ShopifyRequestContext<I18n> {\n const { request } = input;\n\n if (!input.i18n?.country || !input.i18n?.language) {\n throw new Error(\"i18n with country and language is required for Shopify request contexts.\");\n }\n\n if (input.buyerIp !== undefined && !input.buyerIp) {\n throw new Error(\"buyerIp must be non-empty when provided\");\n }\n\n const requestMethod = request.method?.toUpperCase();\n const i18n = normalizeI18n(input.i18n);\n const cookieHeader = request.headers.get(\"cookie\") || undefined;\n const inboundCookies = parseCookieHeader(cookieHeader);\n const hasEssentialCookie = inboundCookies.has(SHOPIFY_ESSENTIAL_COOKIE);\n const isConsentManagementRequest = request.headers.get(CONSENT_MANAGEMENT_HEADER) === \"1\";\n const url = request.url ?? request.headers.get(STOREFRONT_URL_HEADER) ?? undefined;\n const storefrontOrigin = getUrlOrigin(url);\n const context = {\n ...(cookieHeader && { cookie: cookieHeader }),\n globalPrivacyControl: request.headers.get(SEC_GPC_HEADER) ?? undefined,\n i18n,\n ...(url && { url }),\n ...(storefrontOrigin && { storefrontOrigin }),\n ...(input.buyerIp && { buyerIp: input.buyerIp }),\n requestGroupId:\n request.headers.get(REQUEST_GROUP_ID_HEADER) ??\n request.headers.get(\"x-request-id\") ??\n request.headers.get(\"request-id\") ??\n crypto.randomUUID(),\n ...(isDocumentRequest(request) && { documentRequest: true }),\n ...(request.signal && { signal: request.signal }),\n } as Context<I18n>;\n\n let capturedCookies: string[] | undefined;\n let personalizedResponseReason: string | undefined;\n let sessionEstablishingReason: string | undefined;\n\n const captureSubrequestHeaders = (headers: Headers): void => {\n // Capture the first fresh response cookies to increase the\n // chance of returning them from the main server response. The main response\n // needs headers set at send time, while the body can stream later, so this\n // may not be used if subrequests finish after the main response is sent.\n const cookies = headers.getSetCookie();\n if (cookies.length > 0) capturedCookies ??= cookies;\n };\n\n return {\n ...context,\n getForwardedRequestHeaders() {\n const headers = new Headers(request.headers);\n applyStorefrontRequestHeaders(context, headers);\n if (context.url) headers.set(STOREFRONT_URL_HEADER, context.url);\n return headers;\n },\n applyStorefrontRequestHeaders(headers) {\n applyStorefrontRequestHeaders(context, headers);\n },\n captureSubrequestHeaders,\n consumeStorefrontResponseHeaders(headers) {\n captureSubrequestHeaders(headers);\n headers.delete(\"set-cookie\");\n },\n markResponseAsPersonalized(reason) {\n personalizedResponseReason ??= reason;\n },\n markResponseAsSessionEstablishing(reason) {\n sessionEstablishingReason ??= reason;\n },\n applyResponseHeaders(headers) {\n headers.set(\"powered-by\", \"Shopify, Hydrogen\");\n\n // Documents may be shared or streamed, so they must not carry buyer-specific state.\n const isDocumentResponse =\n context.documentRequest || (headers.get(\"content-type\")?.startsWith(\"text/html\") ?? false);\n\n // Keep GET and HEAD responses cacheable, and fail closed when the request method is unknown.\n // Cold sessions may only be established by the explicitly marked consent request.\n const mayReturnShopifyState =\n requestMethod !== undefined &&\n requestMethod !== \"GET\" &&\n requestMethod !== \"HEAD\" &&\n !isDocumentResponse &&\n (hasEssentialCookie ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined);\n\n // Replay cookies captured from fresh SFAPI and proxy responses when allowed.\n if (capturedCookies && mayReturnShopifyState) {\n const existingSetCookies = new Set(headers.getSetCookie());\n for (const value of capturedCookies) {\n if (existingSetCookies.has(value)) continue;\n headers.append(\"set-cookie\", value);\n existingSetCookies.add(value);\n }\n }\n\n // Consent/session responses can contain private state in the body without setting cookies.\n if (\n personalizedResponseReason ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined ||\n headers.has(\"set-cookie\")\n ) {\n applyPrivateResponseCacheHeaders(headers);\n }\n },\n } as ShopifyRequestContext<I18n>;\n}\n\nfunction normalizeI18n<I18n extends I18nConfig>(i18n: I18n): NormalizedI18nConfig<I18n> {\n return {\n ...i18n,\n pathPrefix: normalizePathPrefix(i18n.pathPrefix),\n } as NormalizedI18nConfig<I18n>;\n}\n\nfunction applyStorefrontRequestHeaders(context: Context, headers: Headers): void {\n headers.set(SDK_VARIANT_HEADER, \"hydrogen\");\n headers.set(SDK_VARIANT_SOURCE_HEADER, \"kit\");\n headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);\n headers.set(HYDROGEN_VERSION_HEADER, __HYDROGEN_VERSION__);\n headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);\n\n if (context.cookie) headers.set(\"cookie\", context.cookie);\n else headers.delete(\"cookie\");\n if (context.globalPrivacyControl !== undefined) {\n headers.set(SEC_GPC_HEADER, context.globalPrivacyControl);\n } else headers.delete(SEC_GPC_HEADER);\n if (context.storefrontOrigin) {\n headers.set(SHOPIFY_STOREFRONT_ORIGIN_HEADER, context.storefrontOrigin);\n } else headers.delete(SHOPIFY_STOREFRONT_ORIGIN_HEADER);\n}\n\nfunction getUrlOrigin(url: string | undefined): string | undefined {\n if (!url) return undefined;\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nfunction isDocumentRequest(request: StorefrontRequest): boolean {\n if (request.method && request.method !== \"GET\" && request.method !== \"HEAD\") return false;\n\n const destination = request.headers.get(\"sec-fetch-dest\");\n if (destination === \"document\") return true;\n\n return request.headers.get(\"accept\")?.includes(\"text/html\") ?? false;\n}\n\nfunction parseCookieHeader(cookieHeader: string | undefined): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n\n for (const cookie of cookieHeader.split(\";\")) {\n const separator = cookie.indexOf(\"=\");\n if (separator < 0) continue;\n\n const name = cookie.slice(0, separator).trim();\n if (!cookies.has(name)) cookies.set(name, cookie.slice(separator + 1));\n }\n\n return cookies;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"request-context.mjs","names":[],"sources":["../../src/core/request-context.ts"],"sourcesContent":["import type {\n CountryCode as CustomerAccountCountryCode,\n LanguageCode as CustomerAccountLanguageCode,\n} from \"../graphql/generated/customer-account-api-types\";\nimport type {\n CountryCode as StorefrontCountryCode,\n LanguageCode as StorefrontLanguageCode,\n} from \"../graphql/generated/storefront-api-types\";\nimport { STOREFRONT_API_VERSION } from \"./constants\";\nimport {\n applyPrivateResponseCacheHeaders,\n CONSENT_MANAGEMENT_HEADER,\n HYDROGEN_VERSION_HEADER,\n REQUEST_GROUP_ID_HEADER,\n SDK_VARIANT_HEADER,\n SDK_VARIANT_SOURCE_HEADER,\n SDK_VERSION_HEADER,\n SEC_GPC_HEADER,\n SHOPIFY_STOREFRONT_ORIGIN_HEADER,\n STOREFRONT_URL_HEADER,\n} from \"./headers\";\nimport { normalizePathPrefix } from \"./standard-routes/path\";\n\nconst SHOPIFY_ESSENTIAL_COOKIE = \"_shopify_essential\";\n\n/** The incoming request's headers, plus its method, abort signal, and URL when the framework provides them. */\ntype StorefrontRequest = Pick<Request, \"headers\"> &\n Partial<Pick<Request, \"method\" | \"signal\" | \"url\">>;\n\n/** A language code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyLanguageCode = Extract<StorefrontLanguageCode, CustomerAccountLanguageCode>;\n/** A country code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyCountryCode = Extract<StorefrontCountryCode, CustomerAccountCountryCode>;\n\n/** The language, country, and optional localized path prefix for a request. */\nexport type I18nConfig = {\n /** The request's language. */\n language: ShopifyLanguageCode;\n /** The request's country. */\n country: ShopifyCountryCode;\n /**\n * The URL prefix for localized routes, such as `/es-es`. Hydrogen adds the prefix to standard route redirects and product variant URLs.\n */\n pathPrefix?: string;\n};\n\n/** The request's locale, with a path prefix that starts with `/` or is an empty string. */\ntype NormalizedI18nConfig<I18n extends I18nConfig = I18nConfig> = Omit<I18n, \"pathPrefix\"> & {\n pathPrefix: string;\n};\n\n/** The incoming request and its locale. */\ntype ShopifyRequestContextInputBase<I18n extends I18nConfig = I18nConfig> = {\n /**\n * The incoming request from your framework. When the framework exposes no request, such as during prerendering, pass `{ headers: new Headers() }`.\n */\n request: StorefrontRequest;\n /** The request's language, country, and optional localized path prefix. */\n i18n: I18n;\n};\n\ntype ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp?: never };\n\ntype ShopifyRequestContextWithBuyerIpInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp: string };\n\n/** The fields and methods on every request context. */\ntype ShopifyRequestContextBase = {\n // -- Private fields --\n /**\n * A type-only marker. Create request contexts only with `createShopifyRequestContext`.\n * @internal\n */\n readonly __hydrogenShopifyRequestContextBrand: never;\n /**\n * The incoming request's `Cookie` header. Hydrogen forwards the header to the Storefront API.\n * @internal\n */\n cookie?: string;\n /**\n * The customer's trusted IP address. Private Storefront API clients require it.\n * @internal\n */\n readonly buyerIp?: string;\n /**\n * The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one.\n * @internal\n */\n requestGroupId: string;\n /**\n * The incoming request's abort signal. Hydrogen's API clients stop their requests when the signal aborts.\n * @internal\n */\n signal?: AbortSignal;\n /**\n * The incoming request's URL.\n * @internal\n */\n url?: string;\n /**\n * The origin of the incoming request's URL. Hydrogen forwards the origin to the Storefront API.\n * @internal\n */\n storefrontOrigin?: string;\n /**\n * Adds the Hydrogen SDK headers and the customer's cookie, privacy, and origin headers to a\n * Storefront API request. The Storefront API client and the API proxies call the method for you.\n * @internal\n */\n applyStorefrontRequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from the first Storefront API response that sets cookies. The Storefront API\n * client calls the method for you, and `applyResponseHeaders()` can add the cookies to your response.\n * @internal\n */\n captureSubrequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from a proxied Storefront API response and removes the `set-cookie` header\n * from that response. The API proxies in `handleShopifyRoutes` call the method for you.\n * @internal\n */\n consumeStorefrontResponseHeaders(headers: Headers): void;\n /**\n * Marks the final response as private and uncacheable because the response holds customer data.\n * The Customer Account API client and the customer session call the method for you.\n * @internal\n */\n markResponseAsPersonalized(reason: string): void;\n /**\n * Lets the final response set Shopify cookies before the customer has a Shopify session, and\n * marks the response as private and uncacheable. The UCP MCP proxy calls the method for you.\n * @internal\n */\n markResponseAsSessionEstablishing(reason: string): void;\n\n // -- Public fields --\n /** The request's language, country, and path prefix. The path prefix starts with `/` and has no trailing slash, or is an empty string when you don't set one. */\n i18n: NormalizedI18nConfig;\n /** Returns the incoming request's headers with Shopify's request headers added. Pass the headers when you forward the request to a proxy or another origin. */\n getForwardedRequestHeaders(): Headers;\n /**\n * Adds the headers that a Hydrogen storefront needs to the final response. Call the method on each response that your app builds. Responses from `handleShopifyRoutes` and `handleShopifyRedirects` already carry these headers.\n *\n * Append your committed session headers before you call the method. The method makes any response that sets a cookie private and uncacheable. The method also sets the `powered-by` header, makes responses that hold customer data private and uncacheable, and adds eligible Shopify cookies only to non-document responses for methods other than `GET` and `HEAD`.\n */\n applyResponseHeaders(headers: Headers): void;\n};\n\n/** The context for one request. Pass the context to Hydrogen's API clients and route handlers for that request. */\nexport type ShopifyRequestContext<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextBase & {\n i18n: NormalizedI18nConfig<I18n>;\n };\n\n/** A request context that includes the customer's IP address. Private Storefront API clients require this context. */\nexport type ShopifyRequestContextWithBuyerIp<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContext<I18n> & { readonly buyerIp: string };\n\n/** The values that the request context reads from the incoming request. */\ntype Context<I18n extends I18nConfig = I18nConfig> = {\n /** The incoming request's `Cookie` header. */\n cookie?: string;\n /** The incoming request's `Sec-GPC` header value, which carries the customer's Global Privacy Control signal. */\n globalPrivacyControl?: string;\n /** The customer's trusted IP address. */\n buyerIp?: string;\n /** The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one. */\n requestGroupId: string;\n /** The incoming request's abort signal. */\n signal?: AbortSignal;\n /** The incoming request's URL. */\n url?: string;\n /** The origin of the incoming request's URL. */\n storefrontOrigin?: string;\n /** The request's language, country, and path prefix. */\n i18n: NormalizedI18nConfig<I18n>;\n /** Whether the incoming request asks for an HTML document. */\n documentRequest?: boolean;\n};\n\n/**\n * Creates the context for one incoming request. Pass the same context to the Storefront API\n * client, the Customer Account API client, and `handleShopifyRoutes`. The context forwards the\n * customer's cookies, locale, and privacy signal to Shopify, and adds Shopify's cookies and cache\n * headers to your response.\n *\n * Call `applyResponseHeaders()` on each response that your app builds. Pass `buyerIp` with the customer's trusted IP address when you use a private Storefront API client. The function throws when `i18n` lacks a country or language, and when `buyerIp` is an empty string.\n *\n * @publicDocs\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextWithBuyerIpInput<I18n>,\n): ShopifyRequestContextWithBuyerIp<I18n>;\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInput<I18n>,\n): ShopifyRequestContext<I18n>;\n/**\n * @param input The incoming request, its locale, and the customer's IP address for private Storefront API clients.\n * @returns The context to pass to Hydrogen's API clients and route handlers for this request.\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInputBase<I18n> & { buyerIp?: string },\n): ShopifyRequestContext<I18n> {\n const { request } = input;\n\n if (!input.i18n?.country || !input.i18n?.language) {\n throw new Error(\"i18n with country and language is required for Shopify request contexts.\");\n }\n\n if (input.buyerIp !== undefined && !input.buyerIp) {\n throw new Error(\"buyerIp must be non-empty when provided\");\n }\n\n const requestMethod = request.method?.toUpperCase();\n const i18n = normalizeI18n(input.i18n);\n const cookieHeader = request.headers.get(\"cookie\") || undefined;\n const inboundCookies = parseCookieHeader(cookieHeader);\n const hasEssentialCookie = inboundCookies.has(SHOPIFY_ESSENTIAL_COOKIE);\n const isConsentManagementRequest = request.headers.get(CONSENT_MANAGEMENT_HEADER) === \"1\";\n const url = request.url ?? request.headers.get(STOREFRONT_URL_HEADER) ?? undefined;\n const storefrontOrigin = getUrlOrigin(url);\n const context = {\n ...(cookieHeader && { cookie: cookieHeader }),\n globalPrivacyControl: request.headers.get(SEC_GPC_HEADER) ?? undefined,\n i18n,\n ...(url && { url }),\n ...(storefrontOrigin && { storefrontOrigin }),\n ...(input.buyerIp && { buyerIp: input.buyerIp }),\n requestGroupId:\n request.headers.get(REQUEST_GROUP_ID_HEADER) ??\n request.headers.get(\"x-request-id\") ??\n request.headers.get(\"request-id\") ??\n crypto.randomUUID(),\n ...(isDocumentRequest(request) && { documentRequest: true }),\n ...(request.signal && { signal: request.signal }),\n } as Context<I18n>;\n\n let capturedCookies: string[] | undefined;\n let personalizedResponseReason: string | undefined;\n let sessionEstablishingReason: string | undefined;\n\n const captureSubrequestHeaders = (headers: Headers): void => {\n // Capture the first fresh response cookies to increase the\n // chance of returning them from the main server response. The main response\n // needs headers set at send time, while the body can stream later, so this\n // may not be used if subrequests finish after the main response is sent.\n const cookies = headers.getSetCookie();\n if (cookies.length > 0) capturedCookies ??= cookies;\n };\n\n return {\n ...context,\n getForwardedRequestHeaders() {\n const headers = new Headers(request.headers);\n applyStorefrontRequestHeaders(context, headers);\n if (context.url) headers.set(STOREFRONT_URL_HEADER, context.url);\n return headers;\n },\n applyStorefrontRequestHeaders(headers) {\n applyStorefrontRequestHeaders(context, headers);\n },\n captureSubrequestHeaders,\n consumeStorefrontResponseHeaders(headers) {\n captureSubrequestHeaders(headers);\n headers.delete(\"set-cookie\");\n },\n markResponseAsPersonalized(reason) {\n personalizedResponseReason ??= reason;\n },\n markResponseAsSessionEstablishing(reason) {\n sessionEstablishingReason ??= reason;\n },\n applyResponseHeaders(headers) {\n headers.set(\"powered-by\", \"Shopify, Hydrogen\");\n\n // Documents may be shared or streamed, so they must not carry buyer-specific state.\n const isDocumentResponse =\n context.documentRequest || (headers.get(\"content-type\")?.startsWith(\"text/html\") ?? false);\n\n // Keep GET and HEAD responses cacheable, and fail closed when the request method is unknown.\n // Cold sessions may only be established by the explicitly marked consent request.\n const mayReturnShopifyState =\n requestMethod !== undefined &&\n requestMethod !== \"GET\" &&\n requestMethod !== \"HEAD\" &&\n !isDocumentResponse &&\n (hasEssentialCookie ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined);\n\n // Replay cookies captured from fresh SFAPI and proxy responses when allowed.\n if (capturedCookies && mayReturnShopifyState) {\n const existingSetCookies = new Set(headers.getSetCookie());\n for (const value of capturedCookies) {\n if (existingSetCookies.has(value)) continue;\n headers.append(\"set-cookie\", value);\n existingSetCookies.add(value);\n }\n }\n\n // Consent/session responses can contain private state in the body without setting cookies.\n if (\n personalizedResponseReason ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined ||\n headers.has(\"set-cookie\")\n ) {\n applyPrivateResponseCacheHeaders(headers);\n }\n },\n } as ShopifyRequestContext<I18n>;\n}\n\nfunction normalizeI18n<I18n extends I18nConfig>(i18n: I18n): NormalizedI18nConfig<I18n> {\n return {\n ...i18n,\n pathPrefix: normalizePathPrefix(i18n.pathPrefix),\n } as NormalizedI18nConfig<I18n>;\n}\n\nfunction applyStorefrontRequestHeaders(context: Context, headers: Headers): void {\n headers.set(SDK_VARIANT_HEADER, \"hydrogen\");\n headers.set(SDK_VARIANT_SOURCE_HEADER, \"kit\");\n headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);\n headers.set(HYDROGEN_VERSION_HEADER, __HYDROGEN_VERSION__);\n headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);\n\n if (context.cookie) headers.set(\"cookie\", context.cookie);\n else headers.delete(\"cookie\");\n if (context.globalPrivacyControl !== undefined) {\n headers.set(SEC_GPC_HEADER, context.globalPrivacyControl);\n } else headers.delete(SEC_GPC_HEADER);\n if (context.storefrontOrigin) {\n headers.set(SHOPIFY_STOREFRONT_ORIGIN_HEADER, context.storefrontOrigin);\n } else headers.delete(SHOPIFY_STOREFRONT_ORIGIN_HEADER);\n}\n\nfunction getUrlOrigin(url: string | undefined): string | undefined {\n if (!url) return undefined;\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nfunction isDocumentRequest(request: StorefrontRequest): boolean {\n if (request.method && request.method !== \"GET\" && request.method !== \"HEAD\") return false;\n\n const destination = request.headers.get(\"sec-fetch-dest\");\n if (destination === \"document\") return true;\n\n return request.headers.get(\"accept\")?.includes(\"text/html\") ?? false;\n}\n\nfunction parseCookieHeader(cookieHeader: string | undefined): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n\n for (const cookie of cookieHeader.split(\";\")) {\n const separator = cookie.indexOf(\"=\");\n if (separator < 0) continue;\n\n const name = cookie.slice(0, separator).trim();\n if (!cookies.has(name)) cookies.set(name, cookie.slice(separator + 1));\n }\n\n return cookies;\n}\n"],"mappings":"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"}
@@ -2,14 +2,22 @@ import { StorefrontClient } from "../../client/types.mjs";
2
2
  import { ShopifyRouteTemplates } from "../standard-routes/types.mjs";
3
3
 
4
4
  //#region src/core/request-routing/handle-shopify-redirects.d.ts
5
+ /** The unmatched request, the Storefront API client, and your app's route templates. */
5
6
  type RedirectOptions = {
6
- request: Request;
7
- storefrontClient: StorefrontClient;
7
+ /** The request that your framework's router answered with a `404` response. */request: Request; /** The Storefront API client for the current request. Hydrogen looks up the store's URL redirects with the client, and reads the locale path prefix from the client's request context. */
8
+ storefrontClient: StorefrontClient; /** Your app's route templates. Hydrogen redirects Shopify's default resource paths to these templates. */
8
9
  routeTemplates: ShopifyRouteTemplates;
9
10
  };
10
11
  /**
11
- * Resolves Shopify redirects after framework routing returns a 404. Matched
12
- * responses already include request-context response headers.
12
+ * Finds the Shopify redirect for a request that your framework's router can't match. Call the
13
+ * function when the router returns a `404` response, and return that `404` response when the function
14
+ * resolves to `null`.
15
+ *
16
+ * The function checks these redirects in order and returns the first match as a `301` response: `/admin` to the store's admin, Shopify's default resource paths to your route templates, same-origin `return_to` and `redirect` query parameters, and the store's URL redirects. The response already carries the request context's headers. When a redirect lookup fails, the function logs the error and resolves to `null`.
17
+ *
18
+ * @param options The unmatched request, the Storefront API client, and your app's route templates.
19
+ * @returns A promise for the matched redirect response, or `null` when no redirect matches.
20
+ * @publicDocs
13
21
  */
14
22
  declare function handleShopifyRedirects(options: RedirectOptions): Promise<Response | null>;
15
23
  //#endregion
@@ -8,8 +8,15 @@ import { safeApplyResponseHeaders } from "./safe-apply-response-headers.mjs";
8
8
  //#region src/core/request-routing/handle-shopify-redirects.ts
9
9
  const log = getLogger("redirects");
10
10
  /**
11
- * Resolves Shopify redirects after framework routing returns a 404. Matched
12
- * responses already include request-context response headers.
11
+ * Finds the Shopify redirect for a request that your framework's router can't match. Call the
12
+ * function when the router returns a `404` response, and return that `404` response when the function
13
+ * resolves to `null`.
14
+ *
15
+ * The function checks these redirects in order and returns the first match as a `301` response: `/admin` to the store's admin, Shopify's default resource paths to your route templates, same-origin `return_to` and `redirect` query parameters, and the store's URL redirects. The response already carries the request context's headers. When a redirect lookup fails, the function logs the error and resolves to `null`.
16
+ *
17
+ * @param options The unmatched request, the Storefront API client, and your app's route templates.
18
+ * @returns A promise for the matched redirect response, or `null` when no redirect matches.
19
+ * @publicDocs
13
20
  */
14
21
  async function handleShopifyRedirects(options) {
15
22
  const { request, storefrontClient } = options;