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