@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,18 +1,20 @@
1
1
  //#region src/core/analytics/utils/flatten-connection.d.ts
2
- /**
3
- * Normalizes Shopify's GraphQL connection format into a flat array.
4
- *
5
- * Shopify's Storefront API returns list fields (like cart `lines`) in a
6
- * "connection" wrapper — either `{ nodes: [...] }` or `{ edges: [{ node: ... }] }`.
7
- * Hydrogen queries typically use `nodes`, but queries written with the `edges`
8
- * pattern are supported too.
9
- */
2
+ /** A GraphQL connection in either the nodes shape or the edges shape. */
10
3
  type Connection<T> = {
11
- nodes?: T[];
4
+ /** The connection's items as a flat list. */nodes?: T[]; /** The connection's items, each wrapped in an edge object. */
12
5
  edges?: Array<{
13
6
  node: T;
14
7
  }>;
15
8
  };
9
+ /**
10
+ * Returns the items of a Storefront API connection, such as cart lines, as a flat array.
11
+ *
12
+ * The function accepts the `nodes` shape and the `edges` shape. For the `nodes` shape, the function returns the original array. Copy the result before you sort or change it. The function returns an empty array for `undefined`, `null`, or a connection with neither field. A `null` input also logs a warning.
13
+ *
14
+ * @param connection The Storefront API connection field to flatten.
15
+ * @returns The connection's items as a flat array, or an empty array when there are none.
16
+ * @publicDocs
17
+ */
16
18
  declare function flattenConnection<T>(connection?: Connection<T> | null): T[];
17
19
  //#endregion
18
20
  export { flattenConnection };
@@ -1,15 +1,16 @@
1
1
  import { getLogger } from "../../logging/logging.mjs";
2
2
 
3
3
  //#region src/core/analytics/utils/flatten-connection.ts
4
+ const log = getLogger("analytics");
4
5
  /**
5
- * Normalizes Shopify's GraphQL connection format into a flat array.
6
+ * Returns the items of a Storefront API connection, such as cart lines, as a flat array.
7
+ *
8
+ * The function accepts the `nodes` shape and the `edges` shape. For the `nodes` shape, the function returns the original array. Copy the result before you sort or change it. The function returns an empty array for `undefined`, `null`, or a connection with neither field. A `null` input also logs a warning.
6
9
  *
7
- * Shopify's Storefront API returns list fields (like cart `lines`) in a
8
- * "connection" wrapper — either `{ nodes: [...] }` or `{ edges: [{ node: ... }] }`.
9
- * Hydrogen queries typically use `nodes`, but queries written with the `edges`
10
- * pattern are supported too.
10
+ * @param connection The Storefront API connection field to flatten.
11
+ * @returns The connection's items as a flat array, or an empty array when there are none.
12
+ * @publicDocs
11
13
  */
12
- const log = getLogger("analytics");
13
14
  function flattenConnection(connection) {
14
15
  if (!connection) {
15
16
  if (connection === null) log.warn("received null connection; expected an object with `nodes` or `edges`");
@@ -1 +1 @@
1
- {"version":3,"file":"flatten-connection.mjs","names":[],"sources":["../../../../../src/core/analytics/utils/flatten-connection.ts"],"sourcesContent":["/**\n * Normalizes Shopify's GraphQL connection format into a flat array.\n *\n * Shopify's Storefront API returns list fields (like cart `lines`) in a\n * \"connection\" wrapper — either `{ nodes: [...] }` or `{ edges: [{ node: ... }] }`.\n * Hydrogen queries typically use `nodes`, but queries written with the `edges`\n * pattern are supported too.\n */\n\nimport { getLogger } from \"../../logging\";\n\nconst log = getLogger(\"analytics\");\n\ntype Connection<T> = {\n nodes?: T[];\n edges?: Array<{ node: T }>;\n};\n\nexport function flattenConnection<T>(connection?: Connection<T> | null): T[] {\n if (!connection) {\n if (connection === null) {\n log.warn(\"received null connection; expected an object with `nodes` or `edges`\");\n }\n return [];\n }\n\n if (\"nodes\" in connection && connection.nodes) {\n return connection.nodes;\n }\n\n if (\"edges\" in connection && Array.isArray(connection.edges)) {\n return connection.edges.map((edge) => edge.node);\n }\n\n return [];\n}\n"],"mappings":";;;;;;;;;;;AAWA,MAAM,MAAM,UAAU,YAAY;AAOlC,SAAgB,kBAAqB,YAAwC;AAC3E,KAAI,CAAC,YAAY;AACf,MAAI,eAAe,KACjB,KAAI,KAAK,uEAAuE;AAElF,SAAO,EAAE;;AAGX,KAAI,WAAW,cAAc,WAAW,MACtC,QAAO,WAAW;AAGpB,KAAI,WAAW,cAAc,MAAM,QAAQ,WAAW,MAAM,CAC1D,QAAO,WAAW,MAAM,KAAK,SAAS,KAAK,KAAK;AAGlD,QAAO,EAAE"}
1
+ {"version":3,"file":"flatten-connection.mjs","names":[],"sources":["../../../../../src/core/analytics/utils/flatten-connection.ts"],"sourcesContent":["import { getLogger } from \"../../logging\";\n\nconst log = getLogger(\"analytics\");\n\n/** A GraphQL connection in either the nodes shape or the edges shape. */\ntype Connection<T> = {\n /** The connection's items as a flat list. */\n nodes?: T[];\n /** The connection's items, each wrapped in an edge object. */\n edges?: Array<{ node: T }>;\n};\n\n/**\n * Returns the items of a Storefront API connection, such as cart lines, as a flat array.\n *\n * The function accepts the `nodes` shape and the `edges` shape. For the `nodes` shape, the function returns the original array. Copy the result before you sort or change it. The function returns an empty array for `undefined`, `null`, or a connection with neither field. A `null` input also logs a warning.\n *\n * @param connection The Storefront API connection field to flatten.\n * @returns The connection's items as a flat array, or an empty array when there are none.\n * @publicDocs\n */\nexport function flattenConnection<T>(connection?: Connection<T> | null): T[] {\n if (!connection) {\n if (connection === null) {\n log.warn(\"received null connection; expected an object with `nodes` or `edges`\");\n }\n return [];\n }\n\n if (\"nodes\" in connection && connection.nodes) {\n return connection.nodes;\n }\n\n if (\"edges\" in connection && Array.isArray(connection.edges)) {\n return connection.edges.map((edge) => edge.node);\n }\n\n return [];\n}\n"],"mappings":";;;AAEA,MAAM,MAAM,UAAU,YAAY;;;;;;;;;;AAmBlC,SAAgB,kBAAqB,YAAwC;AAC3E,KAAI,CAAC,YAAY;AACf,MAAI,eAAe,KACjB,KAAI,KAAK,uEAAuE;AAElF,SAAO,EAAE;;AAGX,KAAI,WAAW,cAAc,WAAW,MACtC,QAAO,WAAW;AAGpB,KAAI,WAAW,cAAc,MAAM,QAAQ,WAAW,MAAM,CAC1D,QAAO,WAAW,MAAM,KAAK,SAAS,KAAK,KAAK;AAGlD,QAAO,EAAE"}
@@ -16,24 +16,44 @@ type FetchCacheResponseContext = {
16
16
  json?: () => Promise<unknown>;
17
17
  };
18
18
  type FetchCacheOptions = {
19
- key: CacheKey;
20
- strategy: CachingStrategy;
19
+ /** Identifies the cached response. */key: CacheKey; /** Sets how long the response stays fresh, stale while revalidating, and stale after an error. */
20
+ strategy: CachingStrategy; /** Set to `false` to leave the `Cache-Status` header off the response. */
21
21
  annotateCacheStatus?: boolean;
22
22
  /**
23
- * Runs only for OK responses with text-serializable bodies. Non-OK responses
24
- * and binary/streaming responses are never cached.
23
+ * Decides whether to cache an OK response with a text body. Return `false` to skip caching the
24
+ * response. Hydrogen never caches a response with an error status or a non-text body.
25
25
  */
26
26
  shouldCacheResponse?: (context: FetchCacheResponseContext) => MaybePromise<boolean>;
27
+ /**
28
+ * Creates the signal for a stale-while-revalidate refresh, which runs after
29
+ * the caller already has the stale response. Without it, the refresh reuses
30
+ * `init.signal`.
31
+ */
32
+ backgroundSignal?: () => AbortSignal;
27
33
  };
34
+ /** A fetch function that caches the response when you pass cache options as the third argument. */
28
35
  type FetchWithCache = {
29
36
  (input: FetchInput, init?: FetchInit): Promise<Response>;
30
37
  (input: FetchInput, init: FetchInit | undefined, options: FetchCacheOptions): Promise<Response>;
31
38
  };
39
+ /** Pass a cache and an optional `waitUntil` function, or pass `runWithCache` from `createRunWithCache` to share its cache. Set `fetch` to replace the global fetch function. */
32
40
  type CreateFetchWithCacheOptions = (CreateRunWithCacheOptions | {
33
41
  runWithCache: RunWithCache;
34
42
  }) & {
35
43
  fetch?: typeof fetch;
36
44
  };
45
+ /**
46
+ * Creates a fetch function that caches responses from third-party APIs. Call the function like
47
+ * `fetch()`, and pass a `key` and `strategy` in the third argument to cache the response. Calls
48
+ * without the third argument skip the cache. Return `false` from a `shouldCacheResponse` option to
49
+ * skip caching one response.
50
+ *
51
+ * The function caches only OK responses with no body or with a JSON, text, XML, HTML, GraphQL, or JavaScript content type. Other responses skip the cache. The function removes the `set-cookie` and `server-timing` headers from each cached response, including the response that a cache miss returns. With `staleIfError`, the function serves a stale entry only after a `500`, `502`, `503`, or `504` status, or when the fetch throws.
52
+ *
53
+ * @param options - A cache, or the function from `createRunWithCache` to share its cache, plus an optional fetch function.
54
+ * @returns A fetch function that adds a `Cache-Status` header to calls with cache options, unless `annotateCacheStatus` is `false`.
55
+ * @publicDocs
56
+ */
37
57
  declare function createFetchWithCache({
38
58
  fetch: customFetch,
39
59
  ...options
@@ -27,6 +27,18 @@ var ServerResponseError = class extends Error {
27
27
  this.response = response;
28
28
  }
29
29
  };
30
+ /**
31
+ * Creates a fetch function that caches responses from third-party APIs. Call the function like
32
+ * `fetch()`, and pass a `key` and `strategy` in the third argument to cache the response. Calls
33
+ * without the third argument skip the cache. Return `false` from a `shouldCacheResponse` option to
34
+ * skip caching one response.
35
+ *
36
+ * The function caches only OK responses with no body or with a JSON, text, XML, HTML, GraphQL, or JavaScript content type. Other responses skip the cache. The function removes the `set-cookie` and `server-timing` headers from each cached response, including the response that a cache miss returns. With `staleIfError`, the function serves a stale entry only after a `500`, `502`, `503`, or `504` status, or when the fetch throws.
37
+ *
38
+ * @param options - A cache, or the function from `createRunWithCache` to share its cache, plus an optional fetch function.
39
+ * @returns A fetch function that adds a `Cache-Status` header to calls with cache options, unless `annotateCacheStatus` is `false`.
40
+ * @publicDocs
41
+ */
30
42
  function createFetchWithCache({ fetch: customFetch, ...options }) {
31
43
  const resolvedFetch = customFetch ?? globalThis.fetch;
32
44
  const runWithCache = "runWithCache" in options ? options.runWithCache : createRunWithCache(options);
@@ -39,8 +51,11 @@ function createFetchWithCache({ fetch: customFetch, ...options }) {
39
51
  result = await runWithCache({
40
52
  key: cacheOptions.key,
41
53
  strategy: cacheOptions.strategy
42
- }, async () => {
43
- const response = await resolvedFetch(input, init);
54
+ }, async (context) => {
55
+ const response = await resolvedFetch(input, context?.background && cacheOptions.backgroundSignal ? {
56
+ ...init,
57
+ signal: cacheOptions.backgroundSignal()
58
+ } : init);
44
59
  if (!response.ok) {
45
60
  if (isStaleIfErrorStatus(response.status)) {
46
61
  if (!canSerializeBodyAsText(response)) {
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-with-cache.mjs","names":[],"sources":["../../../../src/core/cache/fetch-with-cache.ts"],"sourcesContent":["import { HYDROGEN_CACHE_STATUS_PRODUCT } from \"./cache-status\";\nimport { type CacheKey } from \"./key\";\nimport {\n type CreateRunWithCacheOptions,\n type RunWithCache,\n type RunWithCacheResult,\n StaleFallbackDisabledError,\n createRunWithCache,\n} from \"./run-with-cache\";\nimport { type SerializableCacheValue } from \"./store\";\nimport { NO_STORE, type CachingStrategy } from \"./strategies\";\n\ntype MaybePromise<T> = T | Promise<T>;\n\ntype FetchInput = Parameters<typeof fetch>[0];\ntype FetchInit = Parameters<typeof fetch>[1];\n\ntype SerializableResponse = SerializableCacheValue & {\n body: string;\n cacheable: boolean;\n headers: [string, string][];\n status: number;\n statusText: string;\n};\n\nexport type FetchCacheResponseContext = {\n ok: boolean;\n status: number;\n statusText: string;\n headers: Headers;\n url: string;\n text(): Promise<string>;\n json?: () => Promise<unknown>;\n};\n\nexport type FetchCacheOptions = {\n key: CacheKey;\n strategy: CachingStrategy;\n annotateCacheStatus?: boolean;\n /**\n * Runs only for OK responses with text-serializable bodies. Non-OK responses\n * and binary/streaming responses are never cached.\n */\n shouldCacheResponse?: (context: FetchCacheResponseContext) => MaybePromise<boolean>;\n};\n\ntype FetchWithCache = {\n (input: FetchInput, init?: FetchInit): Promise<Response>;\n (input: FetchInput, init: FetchInit | undefined, options: FetchCacheOptions): Promise<Response>;\n};\n\ntype CreateFetchWithCacheOptions = (\n | CreateRunWithCacheOptions\n | {\n runWithCache: RunWithCache;\n }\n) & {\n fetch?: typeof fetch;\n};\n\nconst excludedCachedResponseHeaders = new Set([\"set-cookie\", \"server-timing\"]);\nconst staleIfErrorStatuses = new Set([500, 502, 503, 504]);\n\nclass UnserializableResponseError extends StaleFallbackDisabledError {\n constructor() {\n super(\"Response body is not serializable for cache.\");\n }\n}\n\nclass StaleFallbackEligibleResponseError extends Error {\n constructor() {\n super(\"Origin returned an unserializable response eligible for stale-if-error fallback.\");\n }\n}\n\nclass ServerResponseError extends Error {\n readonly response: SerializableResponse;\n\n constructor(response: SerializableResponse) {\n super(\"Origin returned a response eligible for stale-if-error fallback.\");\n this.response = response;\n }\n}\n\nexport function createFetchWithCache({\n fetch: customFetch,\n ...options\n}: CreateFetchWithCacheOptions): FetchWithCache {\n const resolvedFetch = customFetch ?? globalThis.fetch;\n const runWithCache =\n \"runWithCache\" in options ? options.runWithCache : createRunWithCache(options);\n\n return async function fetchWithCache(\n input: FetchInput,\n init?: FetchInit,\n cacheOptions?: FetchCacheOptions,\n ): Promise<Response> {\n if (!cacheOptions) return resolvedFetch(input, init);\n\n if (cacheOptions.strategy.mode === NO_STORE) {\n return annotateResponse(\n await resolvedFetch(input, init),\n getCacheStatusHeader(\"fwd=bypass\"),\n cacheOptions,\n );\n }\n\n let passthroughResponse: Response | undefined;\n let result: RunWithCacheResult<SerializableResponse>;\n\n try {\n result = await runWithCache<SerializableResponse>(\n {\n key: cacheOptions.key,\n strategy: cacheOptions.strategy,\n },\n async () => {\n const response = await resolvedFetch(input, init);\n\n if (!response.ok) {\n if (isStaleIfErrorStatus(response.status)) {\n if (!canSerializeBodyAsText(response)) {\n passthroughResponse = response;\n throw new StaleFallbackEligibleResponseError();\n }\n\n throw new ServerResponseError(\n await serializeResponse(response, {\n cacheable: false,\n fallbackOnError: true,\n }),\n );\n }\n\n return {\n data: await serializeResponse(response, {\n cacheable: false,\n fallbackOnError: true,\n }),\n shouldCache: false,\n };\n }\n\n if (!canSerializeBodyAsText(response)) {\n passthroughResponse = response;\n\n // Signal bypass so the original streaming response can be returned\n // to this caller without attempting to serialize it for storage.\n throw new UnserializableResponseError();\n }\n\n const bodyReader = createBodyReader(response);\n const shouldCache = await shouldCacheResponse(response, bodyReader, cacheOptions);\n\n if (!shouldCache) {\n return {\n data: await serializeResponse(response, {\n bodyReader,\n cacheable: false,\n fallbackOnError: true,\n }),\n shouldCache: false,\n };\n }\n\n const data = await serializeResponse(response, {\n bodyReader,\n cacheable: true,\n fallbackOnError: true,\n });\n\n return {\n data,\n shouldCache: data.cacheable,\n };\n },\n );\n } catch (error) {\n if (error instanceof ServerResponseError) {\n return annotateResponse(\n fromSerializableResponse(error.response),\n getCacheStatusHeader(\"fwd=bypass\"),\n cacheOptions,\n );\n }\n\n if (error instanceof UnserializableResponseError) {\n return annotateResponse(\n // The raw Response body is not cache-serializable, so return the\n // original response. Refetch defensively if the response was not kept.\n passthroughResponse ?? (await resolvedFetch(input, init)),\n getCacheStatusHeader(\"fwd=bypass\"),\n cacheOptions,\n );\n }\n\n if (error instanceof StaleFallbackEligibleResponseError) {\n return annotateResponse(\n // Return the original unserializable response. This error type can\n // also trigger stale-if-error fallback when a stale entry exists.\n passthroughResponse ?? (await resolvedFetch(input, init)),\n getCacheStatusHeader(\"fwd=bypass\"),\n cacheOptions,\n );\n }\n\n throw error;\n }\n\n return annotateResponse(\n fromSerializableResponse(result.data),\n result.cacheStatus === \"hit\"\n ? getCacheStatusHeader(\"hit\")\n : getNonHitCacheStatusHeader(result.data.cacheable, result.cacheStatus),\n cacheOptions,\n );\n };\n}\n\nfunction shouldCacheResponse(\n response: Response,\n bodyReader: BodyReader,\n options: FetchCacheOptions,\n): MaybePromise<boolean> {\n const context: FetchCacheResponseContext = {\n ok: response.ok,\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n url: response.url,\n text: bodyReader.text,\n };\n\n if (isJsonResponse(response)) {\n context.json = bodyReader.json;\n }\n\n return options.shouldCacheResponse?.(context) ?? true;\n}\n\ntype BodyReader = {\n text(): Promise<string>;\n json(): Promise<unknown>;\n};\n\ntype SerializeResponseOptions = {\n bodyReader?: BodyReader;\n cacheable: boolean;\n fallbackOnError?: boolean;\n};\n\nfunction createBodyReader(response: Response): BodyReader {\n let bodyText: Promise<string> | undefined;\n let bodyJson: Promise<unknown> | undefined;\n\n const text = () => {\n bodyText ??= response.text();\n return bodyText;\n };\n\n return {\n text,\n json() {\n bodyJson ??= text().then((value) => JSON.parse(value));\n return bodyJson;\n },\n };\n}\n\nasync function serializeResponse(\n response: Response,\n { bodyReader, cacheable, fallbackOnError = false }: SerializeResponseOptions,\n): Promise<SerializableResponse> {\n if (!canSerializeBodyAsText(response)) return emptySerializableResponse(response);\n\n try {\n return {\n body: await (bodyReader ?? createBodyReader(response)).text(),\n cacheable,\n headers: getSerializableHeaders(response.headers, cacheable),\n status: response.status,\n statusText: response.statusText,\n };\n } catch (error) {\n if (!fallbackOnError) throw error;\n return emptySerializableResponse(response);\n }\n}\n\nfunction fromSerializableResponse(response: SerializableResponse): Response {\n return new Response(response.body, {\n headers: response.headers,\n status: response.status,\n statusText: response.statusText,\n });\n}\n\nfunction emptySerializableResponse(response: Response): SerializableResponse {\n return {\n body: \"\",\n cacheable: false,\n headers: [],\n status: response.status,\n statusText: response.statusText,\n };\n}\n\nfunction getSerializableHeaders(headers: Headers, cacheable: boolean): [string, string][] {\n if (!cacheable) return [...headers];\n\n // Cacheable serialized responses can be returned to future requests, so\n // per-request headers must not be copied.\n return [...headers].filter(([key]) => !excludedCachedResponseHeaders.has(key.toLowerCase()));\n}\n\nfunction annotateResponse(\n response: Response,\n cacheStatus: string,\n options: FetchCacheOptions,\n): Response {\n if (options.annotateCacheStatus === false) return response;\n\n const headers = new Headers(response.headers);\n headers.append(\"Cache-Status\", cacheStatus);\n\n return new Response(response.body, {\n headers,\n status: response.status,\n statusText: response.statusText,\n });\n}\n\nfunction getNonHitCacheStatusHeader(shouldCache: boolean, cacheStatus: \"miss\" | \"bypass\"): string {\n if (cacheStatus === \"bypass\") return getCacheStatusHeader(\"fwd=bypass\");\n return shouldCache\n ? getCacheStatusHeader(\"fwd=uri-miss; stored\")\n : getCacheStatusHeader(\"fwd=bypass\");\n}\n\nfunction getCacheStatusHeader(value: string): string {\n return `${HYDROGEN_CACHE_STATUS_PRODUCT}; ${value}`;\n}\n\nfunction isJsonResponse(response: Response): boolean {\n return response.headers.get(\"content-type\")?.toLowerCase().includes(\"json\") ?? false;\n}\n\nfunction isStaleIfErrorStatus(status: number): boolean {\n return staleIfErrorStatuses.has(status);\n}\n\nfunction canSerializeBodyAsText(response: Response): boolean {\n if (response.body == null) return true;\n\n const contentType = response.headers.get(\"content-type\");\n return contentType != null && /\\b(json|text|xml|html|graphql|javascript)\\b/i.test(contentType);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fetch-with-cache.mjs","names":[],"sources":["../../../../src/core/cache/fetch-with-cache.ts"],"sourcesContent":["import { HYDROGEN_CACHE_STATUS_PRODUCT } from \"./cache-status\";\nimport { type CacheKey } from \"./key\";\nimport {\n type CreateRunWithCacheOptions,\n type RunWithCache,\n type RunWithCacheResult,\n StaleFallbackDisabledError,\n createRunWithCache,\n} from \"./run-with-cache\";\nimport { type SerializableCacheValue } from \"./store\";\nimport { NO_STORE, type CachingStrategy } from \"./strategies\";\n\ntype MaybePromise<T> = T | Promise<T>;\n\ntype FetchInput = Parameters<typeof fetch>[0];\ntype FetchInit = Parameters<typeof fetch>[1];\n\ntype SerializableResponse = SerializableCacheValue & {\n body: string;\n cacheable: boolean;\n headers: [string, string][];\n status: number;\n statusText: string;\n};\n\nexport type FetchCacheResponseContext = {\n ok: boolean;\n status: number;\n statusText: string;\n headers: Headers;\n url: string;\n text(): Promise<string>;\n json?: () => Promise<unknown>;\n};\n\nexport type FetchCacheOptions = {\n /** Identifies the cached response. */\n key: CacheKey;\n /** Sets how long the response stays fresh, stale while revalidating, and stale after an error. */\n strategy: CachingStrategy;\n /** Set to `false` to leave the `Cache-Status` header off the response. */\n annotateCacheStatus?: boolean;\n /**\n * Decides whether to cache an OK response with a text body. Return `false` to skip caching the\n * response. Hydrogen never caches a response with an error status or a non-text body.\n */\n shouldCacheResponse?: (context: FetchCacheResponseContext) => MaybePromise<boolean>;\n /**\n * Creates the signal for a stale-while-revalidate refresh, which runs after\n * the caller already has the stale response. Without it, the refresh reuses\n * `init.signal`.\n */\n backgroundSignal?: () => AbortSignal;\n};\n\n/** A fetch function that caches the response when you pass cache options as the third argument. */\ntype FetchWithCache = {\n (input: FetchInput, init?: FetchInit): Promise<Response>;\n (input: FetchInput, init: FetchInit | undefined, options: FetchCacheOptions): Promise<Response>;\n};\n\n/** Pass a cache and an optional `waitUntil` function, or pass `runWithCache` from `createRunWithCache` to share its cache. Set `fetch` to replace the global fetch function. */\ntype CreateFetchWithCacheOptions = (\n | CreateRunWithCacheOptions\n | {\n runWithCache: RunWithCache;\n }\n) & {\n fetch?: typeof fetch;\n};\n\nconst excludedCachedResponseHeaders = new Set([\"set-cookie\", \"server-timing\"]);\nconst staleIfErrorStatuses = new Set([500, 502, 503, 504]);\n\nclass UnserializableResponseError extends StaleFallbackDisabledError {\n constructor() {\n super(\"Response body is not serializable for cache.\");\n }\n}\n\nclass StaleFallbackEligibleResponseError extends Error {\n constructor() {\n super(\"Origin returned an unserializable response eligible for stale-if-error fallback.\");\n }\n}\n\nclass ServerResponseError extends Error {\n readonly response: SerializableResponse;\n\n constructor(response: SerializableResponse) {\n super(\"Origin returned a response eligible for stale-if-error fallback.\");\n this.response = response;\n }\n}\n\n/**\n * Creates a fetch function that caches responses from third-party APIs. Call the function like\n * `fetch()`, and pass a `key` and `strategy` in the third argument to cache the response. Calls\n * without the third argument skip the cache. Return `false` from a `shouldCacheResponse` option to\n * skip caching one response.\n *\n * The function caches only OK responses with no body or with a JSON, text, XML, HTML, GraphQL, or JavaScript content type. Other responses skip the cache. The function removes the `set-cookie` and `server-timing` headers from each cached response, including the response that a cache miss returns. With `staleIfError`, the function serves a stale entry only after a `500`, `502`, `503`, or `504` status, or when the fetch throws.\n *\n * @param options - A cache, or the function from `createRunWithCache` to share its cache, plus an optional fetch function.\n * @returns A fetch function that adds a `Cache-Status` header to calls with cache options, unless `annotateCacheStatus` is `false`.\n * @publicDocs\n */\nexport function createFetchWithCache({\n fetch: customFetch,\n ...options\n}: CreateFetchWithCacheOptions): FetchWithCache {\n const resolvedFetch = customFetch ?? globalThis.fetch;\n const runWithCache =\n \"runWithCache\" in options ? options.runWithCache : createRunWithCache(options);\n\n return async function fetchWithCache(\n input: FetchInput,\n init?: FetchInit,\n cacheOptions?: FetchCacheOptions,\n ): Promise<Response> {\n if (!cacheOptions) return resolvedFetch(input, init);\n\n if (cacheOptions.strategy.mode === NO_STORE) {\n return annotateResponse(\n await resolvedFetch(input, init),\n getCacheStatusHeader(\"fwd=bypass\"),\n cacheOptions,\n );\n }\n\n let passthroughResponse: Response | undefined;\n let result: RunWithCacheResult<SerializableResponse>;\n\n try {\n result = await runWithCache<SerializableResponse>(\n {\n key: cacheOptions.key,\n strategy: cacheOptions.strategy,\n },\n async (context) => {\n const response = await resolvedFetch(\n input,\n context?.background && cacheOptions.backgroundSignal\n ? { ...init, signal: cacheOptions.backgroundSignal() }\n : init,\n );\n\n if (!response.ok) {\n if (isStaleIfErrorStatus(response.status)) {\n if (!canSerializeBodyAsText(response)) {\n passthroughResponse = response;\n throw new StaleFallbackEligibleResponseError();\n }\n\n throw new ServerResponseError(\n await serializeResponse(response, {\n cacheable: false,\n fallbackOnError: true,\n }),\n );\n }\n\n return {\n data: await serializeResponse(response, {\n cacheable: false,\n fallbackOnError: true,\n }),\n shouldCache: false,\n };\n }\n\n if (!canSerializeBodyAsText(response)) {\n passthroughResponse = response;\n\n // Signal bypass so the original streaming response can be returned\n // to this caller without attempting to serialize it for storage.\n throw new UnserializableResponseError();\n }\n\n const bodyReader = createBodyReader(response);\n const shouldCache = await shouldCacheResponse(response, bodyReader, cacheOptions);\n\n if (!shouldCache) {\n return {\n data: await serializeResponse(response, {\n bodyReader,\n cacheable: false,\n fallbackOnError: true,\n }),\n shouldCache: false,\n };\n }\n\n const data = await serializeResponse(response, {\n bodyReader,\n cacheable: true,\n fallbackOnError: true,\n });\n\n return {\n data,\n shouldCache: data.cacheable,\n };\n },\n );\n } catch (error) {\n if (error instanceof ServerResponseError) {\n return annotateResponse(\n fromSerializableResponse(error.response),\n getCacheStatusHeader(\"fwd=bypass\"),\n cacheOptions,\n );\n }\n\n if (error instanceof UnserializableResponseError) {\n return annotateResponse(\n // The raw Response body is not cache-serializable, so return the\n // original response. Refetch defensively if the response was not kept.\n passthroughResponse ?? (await resolvedFetch(input, init)),\n getCacheStatusHeader(\"fwd=bypass\"),\n cacheOptions,\n );\n }\n\n if (error instanceof StaleFallbackEligibleResponseError) {\n return annotateResponse(\n // Return the original unserializable response. This error type can\n // also trigger stale-if-error fallback when a stale entry exists.\n passthroughResponse ?? (await resolvedFetch(input, init)),\n getCacheStatusHeader(\"fwd=bypass\"),\n cacheOptions,\n );\n }\n\n throw error;\n }\n\n return annotateResponse(\n fromSerializableResponse(result.data),\n result.cacheStatus === \"hit\"\n ? getCacheStatusHeader(\"hit\")\n : getNonHitCacheStatusHeader(result.data.cacheable, result.cacheStatus),\n cacheOptions,\n );\n };\n}\n\nfunction shouldCacheResponse(\n response: Response,\n bodyReader: BodyReader,\n options: FetchCacheOptions,\n): MaybePromise<boolean> {\n const context: FetchCacheResponseContext = {\n ok: response.ok,\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n url: response.url,\n text: bodyReader.text,\n };\n\n if (isJsonResponse(response)) {\n context.json = bodyReader.json;\n }\n\n return options.shouldCacheResponse?.(context) ?? true;\n}\n\ntype BodyReader = {\n text(): Promise<string>;\n json(): Promise<unknown>;\n};\n\ntype SerializeResponseOptions = {\n bodyReader?: BodyReader;\n cacheable: boolean;\n fallbackOnError?: boolean;\n};\n\nfunction createBodyReader(response: Response): BodyReader {\n let bodyText: Promise<string> | undefined;\n let bodyJson: Promise<unknown> | undefined;\n\n const text = () => {\n bodyText ??= response.text();\n return bodyText;\n };\n\n return {\n text,\n json() {\n bodyJson ??= text().then((value) => JSON.parse(value));\n return bodyJson;\n },\n };\n}\n\nasync function serializeResponse(\n response: Response,\n { bodyReader, cacheable, fallbackOnError = false }: SerializeResponseOptions,\n): Promise<SerializableResponse> {\n if (!canSerializeBodyAsText(response)) return emptySerializableResponse(response);\n\n try {\n return {\n body: await (bodyReader ?? createBodyReader(response)).text(),\n cacheable,\n headers: getSerializableHeaders(response.headers, cacheable),\n status: response.status,\n statusText: response.statusText,\n };\n } catch (error) {\n if (!fallbackOnError) throw error;\n return emptySerializableResponse(response);\n }\n}\n\nfunction fromSerializableResponse(response: SerializableResponse): Response {\n return new Response(response.body, {\n headers: response.headers,\n status: response.status,\n statusText: response.statusText,\n });\n}\n\nfunction emptySerializableResponse(response: Response): SerializableResponse {\n return {\n body: \"\",\n cacheable: false,\n headers: [],\n status: response.status,\n statusText: response.statusText,\n };\n}\n\nfunction getSerializableHeaders(headers: Headers, cacheable: boolean): [string, string][] {\n if (!cacheable) return [...headers];\n\n // Cacheable serialized responses can be returned to future requests, so\n // per-request headers must not be copied.\n return [...headers].filter(([key]) => !excludedCachedResponseHeaders.has(key.toLowerCase()));\n}\n\nfunction annotateResponse(\n response: Response,\n cacheStatus: string,\n options: FetchCacheOptions,\n): Response {\n if (options.annotateCacheStatus === false) return response;\n\n const headers = new Headers(response.headers);\n headers.append(\"Cache-Status\", cacheStatus);\n\n return new Response(response.body, {\n headers,\n status: response.status,\n statusText: response.statusText,\n });\n}\n\nfunction getNonHitCacheStatusHeader(shouldCache: boolean, cacheStatus: \"miss\" | \"bypass\"): string {\n if (cacheStatus === \"bypass\") return getCacheStatusHeader(\"fwd=bypass\");\n return shouldCache\n ? getCacheStatusHeader(\"fwd=uri-miss; stored\")\n : getCacheStatusHeader(\"fwd=bypass\");\n}\n\nfunction getCacheStatusHeader(value: string): string {\n return `${HYDROGEN_CACHE_STATUS_PRODUCT}; ${value}`;\n}\n\nfunction isJsonResponse(response: Response): boolean {\n return response.headers.get(\"content-type\")?.toLowerCase().includes(\"json\") ?? false;\n}\n\nfunction isStaleIfErrorStatus(status: number): boolean {\n return staleIfErrorStatuses.has(status);\n}\n\nfunction canSerializeBodyAsText(response: Response): boolean {\n if (response.body == null) return true;\n\n const contentType = response.headers.get(\"content-type\");\n return contentType != null && /\\b(json|text|xml|html|graphql|javascript)\\b/i.test(contentType);\n}\n"],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  //#region src/core/cache/key.d.ts
2
+ /** One entry in an array cache key. The number `1` and the string `"1"` produce different keys. */
2
3
  type CacheKeyPrimitive = string | number | boolean | null;
4
+ /** Identifies a cache entry, as a string or an array of strings, numbers, booleans, and `null`. Include every value that changes the cached data. */
3
5
  type CacheKey = string | readonly CacheKeyPrimitive[];
4
6
  //#endregion
5
7
  export { CacheKey };
@@ -1 +1 @@
1
- {"version":3,"file":"key.mjs","names":[],"sources":["../../../../src/core/cache/key.ts"],"sourcesContent":["export type CacheKeyPrimitive = string | number | boolean | null;\n\nexport type CacheKey = string | readonly CacheKeyPrimitive[];\n\nconst textEncoder = new TextEncoder();\n\nexport async function hashCacheKey(key: CacheKey): Promise<string> {\n const serialized = typeof key === \"string\" ? `string:${key}` : `array:${serializeArrayKey(key)}`;\n const bytes = await crypto.subtle.digest(\"SHA-256\", textEncoder.encode(serialized));\n\n return `h2:${hexEncode(bytes)}`;\n}\n\nfunction serializeArrayKey(value: readonly unknown[]): string {\n const values: string[] = [];\n for (let index = 0; index < value.length; index++) {\n if (!(index in value)) {\n throw new TypeError(\"Cache key arrays cannot contain empty slots.\");\n }\n\n values.push(serializeCacheKeyPrimitive(value[index]));\n }\n\n return `[${values.join(\",\")}]`;\n}\n\nfunction serializeCacheKeyPrimitive(value: unknown): string {\n if (value === null) return \"null\";\n if (typeof value === \"string\") return JSON.stringify(value);\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n\n throw new TypeError(\n `Cache key arrays must contain only primitive values. Received: ${typeof value}`,\n );\n}\n\nfunction hexEncode(bytes: ArrayBuffer): string {\n return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n"],"mappings":";AAIA,MAAM,cAAc,IAAI,aAAa;AAErC,eAAsB,aAAa,KAAgC;CACjE,MAAM,aAAa,OAAO,QAAQ,WAAW,UAAU,QAAQ,SAAS,kBAAkB,IAAI;AAG9F,QAAO,MAAM,UAAU,MAFH,OAAO,OAAO,OAAO,WAAW,YAAY,OAAO,WAAW,CAAC,CAEtD;;AAG/B,SAAS,kBAAkB,OAAmC;CAC5D,MAAM,SAAmB,EAAE;AAC3B,MAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;AACjD,MAAI,EAAE,SAAS,OACb,OAAM,IAAI,UAAU,+CAA+C;AAGrE,SAAO,KAAK,2BAA2B,MAAM,OAAO,CAAC;;AAGvD,QAAO,IAAI,OAAO,KAAK,IAAI,CAAC;;AAG9B,SAAS,2BAA2B,OAAwB;AAC1D,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,MAAM;AAC3D,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAChD,QAAO,OAAO,MAAM;AAGtB,OAAM,IAAI,UACR,kEAAkE,OAAO,QAC1E;;AAGH,SAAS,UAAU,OAA4B;AAC7C,QAAO,CAAC,GAAG,IAAI,WAAW,MAAM,CAAC,CAAC,KAAK,SAAS,KAAK,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG"}
1
+ {"version":3,"file":"key.mjs","names":[],"sources":["../../../../src/core/cache/key.ts"],"sourcesContent":["/** One entry in an array cache key. The number `1` and the string `\"1\"` produce different keys. */\nexport type CacheKeyPrimitive = string | number | boolean | null;\n\n/** Identifies a cache entry, as a string or an array of strings, numbers, booleans, and `null`. Include every value that changes the cached data. */\nexport type CacheKey = string | readonly CacheKeyPrimitive[];\n\nconst textEncoder = new TextEncoder();\n\nexport async function hashCacheKey(key: CacheKey): Promise<string> {\n const serialized = typeof key === \"string\" ? `string:${key}` : `array:${serializeArrayKey(key)}`;\n const bytes = await crypto.subtle.digest(\"SHA-256\", textEncoder.encode(serialized));\n\n return `h2:${hexEncode(bytes)}`;\n}\n\nfunction serializeArrayKey(value: readonly unknown[]): string {\n const values: string[] = [];\n for (let index = 0; index < value.length; index++) {\n if (!(index in value)) {\n throw new TypeError(\"Cache key arrays cannot contain empty slots.\");\n }\n\n values.push(serializeCacheKeyPrimitive(value[index]));\n }\n\n return `[${values.join(\",\")}]`;\n}\n\nfunction serializeCacheKeyPrimitive(value: unknown): string {\n if (value === null) return \"null\";\n if (typeof value === \"string\") return JSON.stringify(value);\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n\n throw new TypeError(\n `Cache key arrays must contain only primitive values. Received: ${typeof value}`,\n );\n}\n\nfunction hexEncode(bytes: ArrayBuffer): string {\n return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n"],"mappings":";AAMA,MAAM,cAAc,IAAI,aAAa;AAErC,eAAsB,aAAa,KAAgC;CACjE,MAAM,aAAa,OAAO,QAAQ,WAAW,UAAU,QAAQ,SAAS,kBAAkB,IAAI;AAG9F,QAAO,MAAM,UAAU,MAFH,OAAO,OAAO,OAAO,WAAW,YAAY,OAAO,WAAW,CAAC,CAEtD;;AAG/B,SAAS,kBAAkB,OAAmC;CAC5D,MAAM,SAAmB,EAAE;AAC3B,MAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;AACjD,MAAI,EAAE,SAAS,OACb,OAAM,IAAI,UAAU,+CAA+C;AAGrE,SAAO,KAAK,2BAA2B,MAAM,OAAO,CAAC;;AAGvD,QAAO,IAAI,OAAO,KAAK,IAAI,CAAC;;AAG9B,SAAS,2BAA2B,OAAwB;AAC1D,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,MAAM;AAC3D,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAChD,QAAO,OAAO,MAAM;AAGtB,OAAM,IAAI,UACR,kEAAkE,OAAO,QAC1E;;AAGH,SAAS,UAAU,OAA4B;AAC7C,QAAO,CAAC,GAAG,IAAI,WAAW,MAAM,CAAC,CAAC,KAAK,SAAS,KAAK,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG"}
@@ -3,30 +3,78 @@ import { CachingStrategy } from "./strategies.mjs";
3
3
  import { KeyValueCacheLike, SerializableCacheValue, WebCacheLike } from "./store.mjs";
4
4
 
5
5
  //#region src/core/cache/run-with-cache.d.ts
6
+ /** A value, or a promise that resolves to the value. */
6
7
  type MaybePromise<T> = T | Promise<T>;
7
- type WaitUntil = (promise: Promise<unknown>) => void;
8
+ /**
9
+ * Keeps the runtime alive until a background cache write or refresh finishes, such as a worker's `waitUntil` function. Hydrogen ignores errors that the function throws.
10
+ */
11
+ type WaitUntil =
12
+ /**
13
+ * @param promise - The background cache write or refresh to wait for.
14
+ * @returns Nothing. Hydrogen ignores the return value.
15
+ */
16
+ (promise: Promise<unknown>) => void;
17
+ /** The cache that holds entries, either a Web Cache API cache or a key-value store. */
8
18
  type CacheInstance = WebCacheLike | KeyValueCacheLike;
19
+ /** The cache and the background-work function for `createRunWithCache`. */
9
20
  type CreateRunWithCacheOptions = {
10
- cache: CacheInstance;
21
+ /** The cache that holds entries. `createRunWithCache` throws a `TypeError` when the cache is neither a Web Cache API cache nor a key-value store. */cache: CacheInstance;
22
+ /**
23
+ * Keeps the runtime alive for background cache writes and refreshes. Without `waitUntil`, each call waits for its cache write before it resolves.
24
+ */
11
25
  waitUntil?: WaitUntil;
12
26
  };
27
+ /** Options for one cached run. */
13
28
  type RunWithCacheOptions = {
14
- key: CacheKey;
29
+ /**
30
+ * Identifies the cache entry. `runWithCache` throws a `TypeError` for an array key with empty slots or with entries other than strings, numbers, booleans, and `null`.
31
+ */
32
+ key: CacheKey; /** Sets how long the result stays fresh, how long the cache serves it stale while refreshing it, and how long the cache serves it stale after an error. */
15
33
  strategy: CachingStrategy;
16
34
  };
35
+ /** The callback's return value, with the data and whether to cache the data. */
17
36
  type CacheDecision<T extends SerializableCacheValue> = {
18
37
  /**
19
- * Must be a JSON-serializable object. The cache store may persist this value
20
- * through `JSON.stringify`; nested serializability is the caller's contract.
38
+ * The data to return and cache. Web Cache stores save the data as JSON.
21
39
  */
22
- data: T;
40
+ data: T; /** Set to `false` to return the data without storing it. */
23
41
  shouldCache: boolean;
24
42
  };
43
+ /** The data, and whether the data came from the cache or the callback. */
25
44
  type RunWithCacheResult<T extends SerializableCacheValue> = {
26
- data: T;
45
+ /** The cached data on a hit, or the callback's data otherwise. */data: T; /** `hit` when the data comes from the cache, `miss` when the callback runs, and `bypass` for a `no-store` strategy. */
27
46
  cacheStatus: "hit" | "miss" | "bypass";
28
47
  };
29
- type RunWithCache = <T extends SerializableCacheValue>(options: RunWithCacheOptions, run: () => MaybePromise<CacheDecision<T>>) => Promise<RunWithCacheResult<T>>;
48
+ /** The context that `runWithCache` passes to its callback. */
49
+ type RunWithCacheContext = {
50
+ /** `true` when the callback refreshes a stale entry after the cache already returned the stale value. */background: boolean;
51
+ };
52
+ /** Produces the data for a cached run and decides whether to cache the data. */
53
+ type RunCallback<T extends SerializableCacheValue> =
54
+ /**
55
+ * @param context - Tells the callback whether it refreshes a stale entry in the background.
56
+ * @returns The data and whether to cache the data, or a promise that resolves to both.
57
+ */
58
+ (context?: RunWithCacheContext) => MaybePromise<CacheDecision<T>>;
59
+ /**
60
+ * Returns cached data for the key, or runs the callback and caches the result. Resolves with the
61
+ * data and the cache status.
62
+ */
63
+ type RunWithCache =
64
+ /**
65
+ * @param options - The cache key and caching strategy for this call.
66
+ * @param run - Produces the data and decides whether to cache it. Runs whenever the cache has no fresh entry. The context's `background` field is `true` during a stale-while-revalidate refresh.
67
+ * @returns The data and the cache status.
68
+ */
69
+ <T extends SerializableCacheValue>(options: RunWithCacheOptions, run: RunCallback<T>) => Promise<RunWithCacheResult<T>>;
70
+ /**
71
+ * Creates a function that caches the result of any async work, such as data that several API calls
72
+ * produce together. For a single fetch response, use `createFetchWithCache`.
73
+ *
74
+ * @param options - The cache, and the function that keeps the runtime alive for background cache writes.
75
+ * @returns A function that returns cached data or runs your callback, and reports the cache status.
76
+ * @publicDocs
77
+ */
30
78
  declare function createRunWithCache({
31
79
  cache,
32
80
  waitUntil
@@ -9,11 +9,19 @@ import { createNormalizedCacheStore } from "./store.mjs";
9
9
  * catches refresh failures, but it should not hide intentional bypasses.
10
10
  */
11
11
  var StaleFallbackDisabledError = class extends Error {};
12
+ /**
13
+ * Creates a function that caches the result of any async work, such as data that several API calls
14
+ * produce together. For a single fetch response, use `createFetchWithCache`.
15
+ *
16
+ * @param options - The cache, and the function that keeps the runtime alive for background cache writes.
17
+ * @returns A function that returns cached data or runs your callback, and reports the cache status.
18
+ * @publicDocs
19
+ */
12
20
  function createRunWithCache({ cache, waitUntil }) {
13
21
  const store = createNormalizedCacheStore(cache);
14
22
  return async function runWithCache(options, run) {
15
23
  if (options.strategy.mode === "no-store") return {
16
- data: (await runAndValidate(run)).data,
24
+ data: (await runAndValidate(run, { background: false })).data,
17
25
  cacheStatus: "bypass"
18
26
  };
19
27
  const cacheKey = await hashCacheKey(options.key);
@@ -39,7 +47,10 @@ function createRunWithCache({ cache, waitUntil }) {
39
47
  return runAndMaybeStore(cacheKey, options, run).result;
40
48
  };
41
49
  function revalidateInBackground(cacheKey, options, run) {
42
- schedule(runAndMaybeStore(cacheKey, options, run, { scheduleCacheWrite: false }).complete);
50
+ schedule(runAndMaybeStore(cacheKey, options, run, {
51
+ scheduleCacheWrite: false,
52
+ background: true
53
+ }).complete);
43
54
  }
44
55
  async function refreshWithStaleFallback(cacheKey, staleValue, options, run) {
45
56
  try {
@@ -52,10 +63,10 @@ function createRunWithCache({ cache, waitUntil }) {
52
63
  };
53
64
  }
54
65
  }
55
- function runAndMaybeStore(key, options, run, { scheduleCacheWrite = true } = {}) {
66
+ function runAndMaybeStore(key, options, run, { scheduleCacheWrite = true, background = false } = {}) {
56
67
  let storePromise = Promise.resolve();
57
68
  const result = Promise.resolve().then(async () => {
58
- const decision = await runAndValidate(run);
69
+ const decision = await runAndValidate(run, { background });
59
70
  if (!shouldStore(options.strategy, decision)) return {
60
71
  data: decision.data,
61
72
  cacheStatus: "miss"
@@ -93,8 +104,8 @@ function shouldStore(strategy, decision) {
93
104
  function shouldFallbackToStale(error) {
94
105
  return !(error instanceof StaleFallbackDisabledError);
95
106
  }
96
- async function runAndValidate(run) {
97
- const decision = await run();
107
+ async function runAndValidate(run, context) {
108
+ const decision = await run(context);
98
109
  if (typeof decision !== "object" || decision == null || !("data" in decision) || !("shouldCache" in decision) || typeof decision.shouldCache !== "boolean") throw new TypeError("runWithCache callback must return {data, shouldCache}.");
99
110
  return decision;
100
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"run-with-cache.mjs","names":[],"sources":["../../../../src/core/cache/run-with-cache.ts"],"sourcesContent":["import { hashCacheKey, type CacheKey } from \"./key\";\nimport {\n type CacheEnvelope,\n type KeyValueCacheLike,\n type SerializableCacheValue,\n type WebCacheLike,\n createNormalizedCacheStore,\n} from \"./store\";\nimport { NO_STORE, type CachingStrategy, getCacheRetentionTtl } from \"./strategies\";\n\ntype MaybePromise<T> = T | Promise<T>;\n\nexport type WaitUntil = (promise: Promise<unknown>) => void;\n\nexport type CacheInstance = WebCacheLike | KeyValueCacheLike;\n\nexport type CreateRunWithCacheOptions = {\n cache: CacheInstance;\n waitUntil?: WaitUntil;\n};\n\nexport type RunWithCacheOptions = {\n key: CacheKey;\n strategy: CachingStrategy;\n};\n\nexport type CacheDecision<T extends SerializableCacheValue> = {\n /**\n * Must be a JSON-serializable object. The cache store may persist this value\n * through `JSON.stringify`; nested serializability is the caller's contract.\n */\n data: T;\n shouldCache: boolean;\n};\n\nexport type RunWithCacheResult<T extends SerializableCacheValue> = {\n data: T;\n cacheStatus: \"hit\" | \"miss\" | \"bypass\";\n};\n\nexport type RunWithCache = <T extends SerializableCacheValue>(\n options: RunWithCacheOptions,\n run: () => MaybePromise<CacheDecision<T>>,\n) => Promise<RunWithCacheResult<T>>;\n\ntype CacheState = \"fresh\" | \"stale\" | \"stale-if-error\" | \"expired\";\n\n/**\n * `result` resolves as soon as user data is available; `complete` also waits for\n * any cache write so background revalidation can be scheduled as one operation.\n */\ntype CacheOperation<T extends SerializableCacheValue = SerializableCacheValue> = {\n result: Promise<RunWithCacheResult<T>>;\n complete: Promise<void>;\n};\n\ntype RunAndMaybeStoreOptions = {\n scheduleCacheWrite?: boolean;\n};\n\n/**\n * Internal control-flow error for adapters that need to escape the cache\n * pipeline without being interpreted as an origin failure. `staleIfError`\n * catches refresh failures, but it should not hide intentional bypasses.\n */\nexport class StaleFallbackDisabledError extends Error {}\n\nexport function createRunWithCache({ cache, waitUntil }: CreateRunWithCacheOptions): RunWithCache {\n const store = createNormalizedCacheStore(cache);\n\n return async function runWithCache<T extends SerializableCacheValue>(\n options: RunWithCacheOptions,\n run: () => MaybePromise<CacheDecision<T>>,\n ): Promise<RunWithCacheResult<T>> {\n if (options.strategy.mode === NO_STORE) {\n return { data: (await runAndValidate(run)).data, cacheStatus: \"bypass\" };\n }\n\n const cacheKey = await hashCacheKey(options.key);\n\n let cached: CacheEnvelope<T> | undefined;\n\n try {\n cached = await store.get<T>(cacheKey);\n } catch {}\n\n if (cached) {\n const state = getCacheState(cached, options.strategy);\n\n if (state === \"fresh\") return { data: cached.value, cacheStatus: \"hit\" };\n\n if (state === \"stale\") {\n revalidateInBackground(cacheKey, options, run);\n return { data: cached.value, cacheStatus: \"hit\" };\n }\n\n if (state === \"stale-if-error\") {\n return refreshWithStaleFallback(cacheKey, cached.value, options, run);\n }\n }\n\n return runAndMaybeStore(cacheKey, options, run).result;\n };\n\n function revalidateInBackground<T extends SerializableCacheValue>(\n cacheKey: string,\n options: RunWithCacheOptions,\n run: () => MaybePromise<CacheDecision<T>>,\n ) {\n const entry = runAndMaybeStore(cacheKey, options, run, { scheduleCacheWrite: false });\n schedule(entry.complete);\n }\n\n async function refreshWithStaleFallback<T extends SerializableCacheValue>(\n cacheKey: string,\n staleValue: T,\n options: RunWithCacheOptions,\n run: () => MaybePromise<CacheDecision<T>>,\n ): Promise<RunWithCacheResult<T>> {\n try {\n return await runAndMaybeStore(cacheKey, options, run).result;\n } catch (error) {\n if (!shouldFallbackToStale(error)) throw error;\n\n return { data: staleValue, cacheStatus: \"hit\" };\n }\n }\n\n function runAndMaybeStore<T extends SerializableCacheValue>(\n key: string,\n options: RunWithCacheOptions,\n run: () => MaybePromise<CacheDecision<T>>,\n { scheduleCacheWrite = true }: RunAndMaybeStoreOptions = {},\n ): CacheOperation<T> {\n // Assigned inside `result` once the callback has produced cacheable data.\n // `complete` reads the same variable later, so it follows the actual write\n // without making the foreground result wait when waitUntil is available.\n let storePromise: Promise<void> = Promise.resolve();\n\n const result = Promise.resolve().then(async (): Promise<RunWithCacheResult<T>> => {\n const decision = await runAndValidate(run);\n if (!shouldStore(options.strategy, decision)) {\n return { data: decision.data, cacheStatus: \"miss\" };\n }\n\n storePromise = storeDecision(key, options, decision).catch(() => {});\n\n if (waitUntil && scheduleCacheWrite) {\n schedule(storePromise);\n } else if (!waitUntil) {\n await storePromise;\n }\n\n return { data: decision.data, cacheStatus: \"miss\" };\n });\n\n return {\n result,\n complete: result\n .then(\n () => storePromise,\n () => undefined,\n )\n .then(() => undefined),\n };\n }\n\n async function storeDecision<T extends SerializableCacheValue>(\n key: string,\n options: RunWithCacheOptions,\n decision: CacheDecision<T>,\n ) {\n await store.set(\n key,\n {\n version: 1,\n value: decision.data,\n storedAt: Date.now(),\n strategy: options.strategy,\n },\n {\n strategy: options.strategy,\n },\n );\n }\n\n function schedule(promise: Promise<unknown>) {\n try {\n waitUntil?.(promise);\n } catch {}\n }\n}\n\nfunction shouldStore<T extends SerializableCacheValue>(\n strategy: CachingStrategy,\n decision: CacheDecision<T>,\n): boolean {\n return decision.shouldCache && getCacheRetentionTtl(strategy) > 0;\n}\n\nfunction shouldFallbackToStale(error: unknown): boolean {\n return !(error instanceof StaleFallbackDisabledError);\n}\n\nasync function runAndValidate<T extends SerializableCacheValue>(\n run: () => MaybePromise<CacheDecision<T>>,\n): Promise<CacheDecision<T>> {\n const decision = await run();\n\n if (\n typeof decision !== \"object\" ||\n decision == null ||\n !(\"data\" in decision) ||\n !(\"shouldCache\" in decision) ||\n typeof decision.shouldCache !== \"boolean\"\n ) {\n throw new TypeError(\"runWithCache callback must return {data, shouldCache}.\");\n }\n\n return decision;\n}\n\nfunction getCacheState<T extends SerializableCacheValue>(\n envelope: CacheEnvelope<T>,\n strategy: CachingStrategy,\n): CacheState {\n const age = Math.max(0, (Date.now() - envelope.storedAt) / 1000);\n const maxAge = strategy.maxAge ?? 0;\n const staleWhileRevalidate = strategy.staleWhileRevalidate ?? 0;\n const staleIfError = strategy.staleIfError ?? 0;\n\n if (age <= maxAge) return \"fresh\";\n if (age <= maxAge + staleWhileRevalidate) return \"stale\";\n if (age <= maxAge + staleWhileRevalidate + staleIfError) {\n return \"stale-if-error\";\n }\n\n return \"expired\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"run-with-cache.mjs","names":[],"sources":["../../../../src/core/cache/run-with-cache.ts"],"sourcesContent":["import { hashCacheKey, type CacheKey } from \"./key\";\nimport {\n type CacheEnvelope,\n type KeyValueCacheLike,\n type SerializableCacheValue,\n type WebCacheLike,\n createNormalizedCacheStore,\n} from \"./store\";\nimport { NO_STORE, type CachingStrategy, getCacheRetentionTtl } from \"./strategies\";\n\n/** A value, or a promise that resolves to the value. */\ntype MaybePromise<T> = T | Promise<T>;\n\n/**\n * Keeps the runtime alive until a background cache write or refresh finishes, such as a worker's `waitUntil` function. Hydrogen ignores errors that the function throws.\n */\nexport type WaitUntil =\n /**\n * @param promise - The background cache write or refresh to wait for.\n * @returns Nothing. Hydrogen ignores the return value.\n */\n (promise: Promise<unknown>) => void;\n\n/** The cache that holds entries, either a Web Cache API cache or a key-value store. */\nexport type CacheInstance = WebCacheLike | KeyValueCacheLike;\n\n/** The cache and the background-work function for `createRunWithCache`. */\nexport type CreateRunWithCacheOptions = {\n /** The cache that holds entries. `createRunWithCache` throws a `TypeError` when the cache is neither a Web Cache API cache nor a key-value store. */\n cache: CacheInstance;\n /**\n * Keeps the runtime alive for background cache writes and refreshes. Without `waitUntil`, each call waits for its cache write before it resolves.\n */\n waitUntil?: WaitUntil;\n};\n\n/** Options for one cached run. */\nexport type RunWithCacheOptions = {\n /**\n * Identifies the cache entry. `runWithCache` throws a `TypeError` for an array key with empty slots or with entries other than strings, numbers, booleans, and `null`.\n */\n key: CacheKey;\n /** Sets how long the result stays fresh, how long the cache serves it stale while refreshing it, and how long the cache serves it stale after an error. */\n strategy: CachingStrategy;\n};\n\n/** The callback's return value, with the data and whether to cache the data. */\nexport type CacheDecision<T extends SerializableCacheValue> = {\n /**\n * The data to return and cache. Web Cache stores save the data as JSON.\n */\n data: T;\n /** Set to `false` to return the data without storing it. */\n shouldCache: boolean;\n};\n\n/** The data, and whether the data came from the cache or the callback. */\nexport type RunWithCacheResult<T extends SerializableCacheValue> = {\n /** The cached data on a hit, or the callback's data otherwise. */\n data: T;\n /** `hit` when the data comes from the cache, `miss` when the callback runs, and `bypass` for a `no-store` strategy. */\n cacheStatus: \"hit\" | \"miss\" | \"bypass\";\n};\n\n/** The context that `runWithCache` passes to its callback. */\nexport type RunWithCacheContext = {\n /** `true` when the callback refreshes a stale entry after the cache already returned the stale value. */\n background: boolean;\n};\n\n// The context is optional so custom runners that call `run()` keep working.\n/** Produces the data for a cached run and decides whether to cache the data. */\ntype RunCallback<T extends SerializableCacheValue> =\n /**\n * @param context - Tells the callback whether it refreshes a stale entry in the background.\n * @returns The data and whether to cache the data, or a promise that resolves to both.\n */\n (context?: RunWithCacheContext) => MaybePromise<CacheDecision<T>>;\n\n/**\n * Returns cached data for the key, or runs the callback and caches the result. Resolves with the\n * data and the cache status.\n */\nexport type RunWithCache =\n /**\n * @param options - The cache key and caching strategy for this call.\n * @param run - Produces the data and decides whether to cache it. Runs whenever the cache has no fresh entry. The context's `background` field is `true` during a stale-while-revalidate refresh.\n * @returns The data and the cache status.\n */\n <T extends SerializableCacheValue>(\n options: RunWithCacheOptions,\n run: RunCallback<T>,\n ) => Promise<RunWithCacheResult<T>>;\n\ntype CacheState = \"fresh\" | \"stale\" | \"stale-if-error\" | \"expired\";\n\n/**\n * `result` resolves as soon as user data is available; `complete` also waits for\n * any cache write so background revalidation can be scheduled as one operation.\n */\ntype CacheOperation<T extends SerializableCacheValue = SerializableCacheValue> = {\n result: Promise<RunWithCacheResult<T>>;\n complete: Promise<void>;\n};\n\ntype RunAndMaybeStoreOptions = {\n scheduleCacheWrite?: boolean;\n background?: boolean;\n};\n\n/**\n * Internal control-flow error for adapters that need to escape the cache\n * pipeline without being interpreted as an origin failure. `staleIfError`\n * catches refresh failures, but it should not hide intentional bypasses.\n */\nexport class StaleFallbackDisabledError extends Error {}\n\n/**\n * Creates a function that caches the result of any async work, such as data that several API calls\n * produce together. For a single fetch response, use `createFetchWithCache`.\n *\n * @param options - The cache, and the function that keeps the runtime alive for background cache writes.\n * @returns A function that returns cached data or runs your callback, and reports the cache status.\n * @publicDocs\n */\nexport function createRunWithCache({ cache, waitUntil }: CreateRunWithCacheOptions): RunWithCache {\n const store = createNormalizedCacheStore(cache);\n\n return async function runWithCache<T extends SerializableCacheValue>(\n options: RunWithCacheOptions,\n run: RunCallback<T>,\n ): Promise<RunWithCacheResult<T>> {\n if (options.strategy.mode === NO_STORE) {\n return {\n data: (await runAndValidate(run, { background: false })).data,\n cacheStatus: \"bypass\",\n };\n }\n\n const cacheKey = await hashCacheKey(options.key);\n\n let cached: CacheEnvelope<T> | undefined;\n\n try {\n cached = await store.get<T>(cacheKey);\n } catch {}\n\n if (cached) {\n const state = getCacheState(cached, options.strategy);\n\n if (state === \"fresh\") return { data: cached.value, cacheStatus: \"hit\" };\n\n if (state === \"stale\") {\n revalidateInBackground(cacheKey, options, run);\n return { data: cached.value, cacheStatus: \"hit\" };\n }\n\n if (state === \"stale-if-error\") {\n return refreshWithStaleFallback(cacheKey, cached.value, options, run);\n }\n }\n\n return runAndMaybeStore(cacheKey, options, run).result;\n };\n\n function revalidateInBackground<T extends SerializableCacheValue>(\n cacheKey: string,\n options: RunWithCacheOptions,\n run: RunCallback<T>,\n ) {\n const entry = runAndMaybeStore(cacheKey, options, run, {\n scheduleCacheWrite: false,\n background: true,\n });\n schedule(entry.complete);\n }\n\n async function refreshWithStaleFallback<T extends SerializableCacheValue>(\n cacheKey: string,\n staleValue: T,\n options: RunWithCacheOptions,\n run: RunCallback<T>,\n ): Promise<RunWithCacheResult<T>> {\n try {\n return await runAndMaybeStore(cacheKey, options, run).result;\n } catch (error) {\n if (!shouldFallbackToStale(error)) throw error;\n\n return { data: staleValue, cacheStatus: \"hit\" };\n }\n }\n\n function runAndMaybeStore<T extends SerializableCacheValue>(\n key: string,\n options: RunWithCacheOptions,\n run: RunCallback<T>,\n { scheduleCacheWrite = true, background = false }: RunAndMaybeStoreOptions = {},\n ): CacheOperation<T> {\n // Assigned inside `result` once the callback has produced cacheable data.\n // `complete` reads the same variable later, so it follows the actual write\n // without making the foreground result wait when waitUntil is available.\n let storePromise: Promise<void> = Promise.resolve();\n\n const result = Promise.resolve().then(async (): Promise<RunWithCacheResult<T>> => {\n const decision = await runAndValidate(run, { background });\n if (!shouldStore(options.strategy, decision)) {\n return { data: decision.data, cacheStatus: \"miss\" };\n }\n\n storePromise = storeDecision(key, options, decision).catch(() => {});\n\n if (waitUntil && scheduleCacheWrite) {\n schedule(storePromise);\n } else if (!waitUntil) {\n await storePromise;\n }\n\n return { data: decision.data, cacheStatus: \"miss\" };\n });\n\n return {\n result,\n complete: result\n .then(\n () => storePromise,\n () => undefined,\n )\n .then(() => undefined),\n };\n }\n\n async function storeDecision<T extends SerializableCacheValue>(\n key: string,\n options: RunWithCacheOptions,\n decision: CacheDecision<T>,\n ) {\n await store.set(\n key,\n {\n version: 1,\n value: decision.data,\n storedAt: Date.now(),\n strategy: options.strategy,\n },\n {\n strategy: options.strategy,\n },\n );\n }\n\n function schedule(promise: Promise<unknown>) {\n try {\n waitUntil?.(promise);\n } catch {}\n }\n}\n\nfunction shouldStore<T extends SerializableCacheValue>(\n strategy: CachingStrategy,\n decision: CacheDecision<T>,\n): boolean {\n return decision.shouldCache && getCacheRetentionTtl(strategy) > 0;\n}\n\nfunction shouldFallbackToStale(error: unknown): boolean {\n return !(error instanceof StaleFallbackDisabledError);\n}\n\nasync function runAndValidate<T extends SerializableCacheValue>(\n run: RunCallback<T>,\n context: RunWithCacheContext,\n): Promise<CacheDecision<T>> {\n const decision = await run(context);\n\n if (\n typeof decision !== \"object\" ||\n decision == null ||\n !(\"data\" in decision) ||\n !(\"shouldCache\" in decision) ||\n typeof decision.shouldCache !== \"boolean\"\n ) {\n throw new TypeError(\"runWithCache callback must return {data, shouldCache}.\");\n }\n\n return decision;\n}\n\nfunction getCacheState<T extends SerializableCacheValue>(\n envelope: CacheEnvelope<T>,\n strategy: CachingStrategy,\n): CacheState {\n const age = Math.max(0, (Date.now() - envelope.storedAt) / 1000);\n const maxAge = strategy.maxAge ?? 0;\n const staleWhileRevalidate = strategy.staleWhileRevalidate ?? 0;\n const staleIfError = strategy.staleIfError ?? 0;\n\n if (age <= maxAge) return \"fresh\";\n if (age <= maxAge + staleWhileRevalidate) return \"stale\";\n if (age <= maxAge + staleWhileRevalidate + staleIfError) {\n return \"stale-if-error\";\n }\n\n return \"expired\";\n}\n\n/**\n * Returns cached data for the key, or runs the callback and caches the result.\n *\n * The callback returns an object with `data` and a boolean `shouldCache`. The function throws a `TypeError` for any other shape. During the `staleWhileRevalidate` window, the function returns the stale data right away and runs the callback in the background. During the `staleIfError` window, the function returns the stale data when the callback throws. A `no-store` strategy runs the callback and skips the cache.\n *\n * @publicDocs\n */\nexport type RunWithCacheForDocs =\n /**\n * @param options - The cache key and caching strategy for this call.\n * @param run - Produces the data and decides whether to cache it. Runs whenever the cache has no fresh entry.\n * @returns The data, and whether the data came from the cache, the callback, or a bypass.\n */\n (\n options: RunWithCacheOptions,\n run: () => MaybePromise<CacheDecision<SerializableCacheValue>>,\n ) => Promise<RunWithCacheResult<SerializableCacheValue>>;\n"],"mappings":"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"}
@@ -1,17 +1,19 @@
1
1
  //#region src/core/cache/store.d.ts
2
2
  /**
3
- * Cache values must be JSON-serializable because Web Cache stores serialize
4
- * envelopes before persistence. Callers are responsible for returning data that
5
- * can safely round-trip through `JSON.stringify`.
3
+ * The plain object that you cache. Web Cache stores save the object as JSON. Return data that
4
+ * survives a round trip through `JSON.stringify`.
6
5
  */
7
6
  type SerializableCacheValue = Record<string, unknown>;
7
+ /** A cache with the `match()`, `put()`, and `delete()` methods of the Web Cache API, such as a cache from `caches.open()`. */
8
8
  type WebCacheLike = Pick<Cache, "match" | "put" | "delete">;
9
+ /** Options that Hydrogen passes when it writes an entry to a key-value store. */
9
10
  type KeyValueCacheSetOptions = {
10
- ttl?: number;
11
+ /** How many seconds to keep the entry. Hydrogen passes the sum of the strategy's `maxAge`, `staleWhileRevalidate`, and `staleIfError`, rounded up. */ttl?: number;
11
12
  };
13
+ /** A key-value store with `get()`, `set()`, and an optional `delete()` method. Hydrogen passes each entry to `set()` as an object. Serialize the object in your store when the store saves only strings. */
12
14
  type KeyValueCacheLike = {
13
- get(key: string): unknown | Promise<unknown>;
14
- set(key: string, value: unknown, options?: KeyValueCacheSetOptions): unknown | Promise<unknown>;
15
+ /** Returns the value under the key. Hydrogen treats a value that it didn't write as a cache miss. */get(key: string): unknown | Promise<unknown>; /** Saves the value under the key. Expire the entry after the `ttl` option, in seconds. */
16
+ set(key: string, value: unknown, options?: KeyValueCacheSetOptions): unknown | Promise<unknown>; /** Removes the value under the key. */
15
17
  delete?(key: string): unknown | Promise<unknown>;
16
18
  };
17
19
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"store.mjs","names":[],"sources":["../../../../src/core/cache/store.ts"],"sourcesContent":["import {\n type CachingStrategy,\n getPlatformCacheControlHeader,\n getCacheRetentionTtl,\n} from \"./strategies\";\n\n/**\n * Cache values must be JSON-serializable because Web Cache stores serialize\n * envelopes before persistence. Callers are responsible for returning data that\n * can safely round-trip through `JSON.stringify`.\n */\nexport type SerializableCacheValue = Record<string, unknown>;\n\nexport type CacheEnvelope<T extends SerializableCacheValue = SerializableCacheValue> = {\n version: 1;\n value: T;\n storedAt: number;\n strategy: CachingStrategy;\n};\n\nexport type WebCacheLike = Pick<Cache, \"match\" | \"put\" | \"delete\">;\n\nexport type KeyValueCacheSetOptions = {\n ttl?: number;\n};\n\nexport type KeyValueCacheLike = {\n get(key: string): unknown | Promise<unknown>;\n set(key: string, value: unknown, options?: KeyValueCacheSetOptions): unknown | Promise<unknown>;\n delete?(key: string): unknown | Promise<unknown>;\n};\n\ntype NormalizedCacheStore = {\n get<T extends SerializableCacheValue>(key: string): Promise<CacheEnvelope<T> | undefined>;\n set<T extends SerializableCacheValue>(\n key: string,\n envelope: CacheEnvelope<T>,\n options: NormalizedCacheSetOptions,\n ): Promise<void>;\n delete?(key: string): Promise<void>;\n};\n\ntype NormalizedCacheSetOptions = {\n strategy: CachingStrategy;\n};\n\nexport function createNormalizedCacheStore(\n cache: WebCacheLike | KeyValueCacheLike,\n): NormalizedCacheStore {\n if (isWebCacheLike(cache)) return createWebCacheStore(cache);\n if (isKeyValueCacheLike(cache)) return createKeyValueCacheStore(cache);\n\n throw new TypeError(\"Expected a Web Cache API compatible store or a key/value cache store.\");\n}\n\nfunction createWebCacheStore(cache: WebCacheLike): NormalizedCacheStore {\n return {\n async get<T extends SerializableCacheValue>(key: string) {\n const response = await cache.match(createCacheRequest(key));\n if (!response) return undefined;\n\n const envelope: unknown = JSON.parse(await response.text());\n return isCacheEnvelope<T>(envelope) ? envelope : undefined;\n },\n\n async set<T extends SerializableCacheValue>(\n key: string,\n envelope: CacheEnvelope<T>,\n { strategy }: NormalizedCacheSetOptions,\n ) {\n const response = new Response(JSON.stringify(envelope), {\n headers: {\n \"cache-control\": getPlatformCacheControlHeader(strategy),\n \"content-type\": \"application/json\",\n },\n });\n\n await cache.put(createCacheRequest(key), response);\n },\n\n async delete(key) {\n await cache.delete(createCacheRequest(key));\n },\n };\n}\n\nfunction createKeyValueCacheStore(cache: KeyValueCacheLike): NormalizedCacheStore {\n return {\n async get<T extends SerializableCacheValue>(key: string) {\n const envelope = await cache.get(key);\n return isCacheEnvelope<T>(envelope) ? envelope : undefined;\n },\n\n async set<T extends SerializableCacheValue>(\n key: string,\n envelope: CacheEnvelope<T>,\n { strategy }: NormalizedCacheSetOptions,\n ) {\n const ttl = Math.ceil(getCacheRetentionTtl(strategy));\n\n await cache.set(key, envelope, { ttl });\n },\n\n async delete(key) {\n await cache.delete?.(key);\n },\n };\n}\n\nfunction createCacheRequest(key: string) {\n return new Request(`https://hydrogen.cache/${key}`);\n}\n\nfunction isWebCacheLike(cache: unknown): cache is WebCacheLike {\n return (\n typeof cache === \"object\" &&\n cache != null &&\n \"match\" in cache &&\n \"put\" in cache &&\n typeof cache.match === \"function\" &&\n typeof cache.put === \"function\"\n );\n}\n\nfunction isKeyValueCacheLike(cache: unknown): cache is KeyValueCacheLike {\n return (\n typeof cache === \"object\" &&\n cache != null &&\n \"get\" in cache &&\n \"set\" in cache &&\n typeof cache.get === \"function\" &&\n typeof cache.set === \"function\"\n );\n}\n\nfunction isCacheEnvelope<T extends SerializableCacheValue>(\n value: unknown,\n): value is CacheEnvelope<T> {\n return (\n typeof value === \"object\" &&\n value != null &&\n \"version\" in value &&\n value.version === 1 &&\n \"storedAt\" in value &&\n typeof value.storedAt === \"number\" &&\n // Freshness uses the call-site strategy, but stored envelopes should still\n // look like the current shape so older or malformed entries do not become hits.\n \"strategy\" in value &&\n Boolean(value.strategy) &&\n \"value\" in value &&\n isSerializableCacheValue(value.value)\n );\n}\n\nfunction isSerializableCacheValue(value: unknown): value is SerializableCacheValue {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\n"],"mappings":";;;AA8CA,SAAgB,2BACd,OACsB;AACtB,KAAI,eAAe,MAAM,CAAE,QAAO,oBAAoB,MAAM;AAC5D,KAAI,oBAAoB,MAAM,CAAE,QAAO,yBAAyB,MAAM;AAEtE,OAAM,IAAI,UAAU,wEAAwE;;AAG9F,SAAS,oBAAoB,OAA2C;AACtE,QAAO;EACL,MAAM,IAAsC,KAAa;GACvD,MAAM,WAAW,MAAM,MAAM,MAAM,mBAAmB,IAAI,CAAC;AAC3D,OAAI,CAAC,SAAU,QAAO;GAEtB,MAAM,WAAoB,KAAK,MAAM,MAAM,SAAS,MAAM,CAAC;AAC3D,UAAO,gBAAmB,SAAS,GAAG,WAAW;;EAGnD,MAAM,IACJ,KACA,UACA,EAAE,YACF;GACA,MAAM,WAAW,IAAI,SAAS,KAAK,UAAU,SAAS,EAAE,EACtD,SAAS;IACP,iBAAiB,8BAA8B,SAAS;IACxD,gBAAgB;IACjB,EACF,CAAC;AAEF,SAAM,MAAM,IAAI,mBAAmB,IAAI,EAAE,SAAS;;EAGpD,MAAM,OAAO,KAAK;AAChB,SAAM,MAAM,OAAO,mBAAmB,IAAI,CAAC;;EAE9C;;AAGH,SAAS,yBAAyB,OAAgD;AAChF,QAAO;EACL,MAAM,IAAsC,KAAa;GACvD,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;AACrC,UAAO,gBAAmB,SAAS,GAAG,WAAW;;EAGnD,MAAM,IACJ,KACA,UACA,EAAE,YACF;GACA,MAAM,MAAM,KAAK,KAAK,qBAAqB,SAAS,CAAC;AAErD,SAAM,MAAM,IAAI,KAAK,UAAU,EAAE,KAAK,CAAC;;EAGzC,MAAM,OAAO,KAAK;AAChB,SAAM,MAAM,SAAS,IAAI;;EAE5B;;AAGH,SAAS,mBAAmB,KAAa;AACvC,QAAO,IAAI,QAAQ,0BAA0B,MAAM;;AAGrD,SAAS,eAAe,OAAuC;AAC7D,QACE,OAAO,UAAU,YACjB,SAAS,QACT,WAAW,SACX,SAAS,SACT,OAAO,MAAM,UAAU,cACvB,OAAO,MAAM,QAAQ;;AAIzB,SAAS,oBAAoB,OAA4C;AACvE,QACE,OAAO,UAAU,YACjB,SAAS,QACT,SAAS,SACT,SAAS,SACT,OAAO,MAAM,QAAQ,cACrB,OAAO,MAAM,QAAQ;;AAIzB,SAAS,gBACP,OAC2B;AAC3B,QACE,OAAO,UAAU,YACjB,SAAS,QACT,aAAa,SACb,MAAM,YAAY,KAClB,cAAc,SACd,OAAO,MAAM,aAAa,YAG1B,cAAc,SACd,QAAQ,MAAM,SAAS,IACvB,WAAW,SACX,yBAAyB,MAAM,MAAM;;AAIzC,SAAS,yBAAyB,OAAiD;AACjF,QAAO,OAAO,UAAU,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,MAAM"}
1
+ {"version":3,"file":"store.mjs","names":[],"sources":["../../../../src/core/cache/store.ts"],"sourcesContent":["import {\n type CachingStrategy,\n getPlatformCacheControlHeader,\n getCacheRetentionTtl,\n} from \"./strategies\";\n\n/**\n * The plain object that you cache. Web Cache stores save the object as JSON. Return data that\n * survives a round trip through `JSON.stringify`.\n */\nexport type SerializableCacheValue = Record<string, unknown>;\n\nexport type CacheEnvelope<T extends SerializableCacheValue = SerializableCacheValue> = {\n version: 1;\n value: T;\n storedAt: number;\n strategy: CachingStrategy;\n};\n\n/** A cache with the `match()`, `put()`, and `delete()` methods of the Web Cache API, such as a cache from `caches.open()`. */\nexport type WebCacheLike = Pick<Cache, \"match\" | \"put\" | \"delete\">;\n\n/** Options that Hydrogen passes when it writes an entry to a key-value store. */\nexport type KeyValueCacheSetOptions = {\n /** How many seconds to keep the entry. Hydrogen passes the sum of the strategy's `maxAge`, `staleWhileRevalidate`, and `staleIfError`, rounded up. */\n ttl?: number;\n};\n\n/** A key-value store with `get()`, `set()`, and an optional `delete()` method. Hydrogen passes each entry to `set()` as an object. Serialize the object in your store when the store saves only strings. */\nexport type KeyValueCacheLike = {\n /** Returns the value under the key. Hydrogen treats a value that it didn't write as a cache miss. */\n get(key: string): unknown | Promise<unknown>;\n /** Saves the value under the key. Expire the entry after the `ttl` option, in seconds. */\n set(key: string, value: unknown, options?: KeyValueCacheSetOptions): unknown | Promise<unknown>;\n /** Removes the value under the key. */\n delete?(key: string): unknown | Promise<unknown>;\n};\n\ntype NormalizedCacheStore = {\n get<T extends SerializableCacheValue>(key: string): Promise<CacheEnvelope<T> | undefined>;\n set<T extends SerializableCacheValue>(\n key: string,\n envelope: CacheEnvelope<T>,\n options: NormalizedCacheSetOptions,\n ): Promise<void>;\n delete?(key: string): Promise<void>;\n};\n\ntype NormalizedCacheSetOptions = {\n strategy: CachingStrategy;\n};\n\nexport function createNormalizedCacheStore(\n cache: WebCacheLike | KeyValueCacheLike,\n): NormalizedCacheStore {\n if (isWebCacheLike(cache)) return createWebCacheStore(cache);\n if (isKeyValueCacheLike(cache)) return createKeyValueCacheStore(cache);\n\n throw new TypeError(\"Expected a Web Cache API compatible store or a key/value cache store.\");\n}\n\nfunction createWebCacheStore(cache: WebCacheLike): NormalizedCacheStore {\n return {\n async get<T extends SerializableCacheValue>(key: string) {\n const response = await cache.match(createCacheRequest(key));\n if (!response) return undefined;\n\n const envelope: unknown = JSON.parse(await response.text());\n return isCacheEnvelope<T>(envelope) ? envelope : undefined;\n },\n\n async set<T extends SerializableCacheValue>(\n key: string,\n envelope: CacheEnvelope<T>,\n { strategy }: NormalizedCacheSetOptions,\n ) {\n const response = new Response(JSON.stringify(envelope), {\n headers: {\n \"cache-control\": getPlatformCacheControlHeader(strategy),\n \"content-type\": \"application/json\",\n },\n });\n\n await cache.put(createCacheRequest(key), response);\n },\n\n async delete(key) {\n await cache.delete(createCacheRequest(key));\n },\n };\n}\n\nfunction createKeyValueCacheStore(cache: KeyValueCacheLike): NormalizedCacheStore {\n return {\n async get<T extends SerializableCacheValue>(key: string) {\n const envelope = await cache.get(key);\n return isCacheEnvelope<T>(envelope) ? envelope : undefined;\n },\n\n async set<T extends SerializableCacheValue>(\n key: string,\n envelope: CacheEnvelope<T>,\n { strategy }: NormalizedCacheSetOptions,\n ) {\n const ttl = Math.ceil(getCacheRetentionTtl(strategy));\n\n await cache.set(key, envelope, { ttl });\n },\n\n async delete(key) {\n await cache.delete?.(key);\n },\n };\n}\n\nfunction createCacheRequest(key: string) {\n return new Request(`https://hydrogen.cache/${key}`);\n}\n\nfunction isWebCacheLike(cache: unknown): cache is WebCacheLike {\n return (\n typeof cache === \"object\" &&\n cache != null &&\n \"match\" in cache &&\n \"put\" in cache &&\n typeof cache.match === \"function\" &&\n typeof cache.put === \"function\"\n );\n}\n\nfunction isKeyValueCacheLike(cache: unknown): cache is KeyValueCacheLike {\n return (\n typeof cache === \"object\" &&\n cache != null &&\n \"get\" in cache &&\n \"set\" in cache &&\n typeof cache.get === \"function\" &&\n typeof cache.set === \"function\"\n );\n}\n\nfunction isCacheEnvelope<T extends SerializableCacheValue>(\n value: unknown,\n): value is CacheEnvelope<T> {\n return (\n typeof value === \"object\" &&\n value != null &&\n \"version\" in value &&\n value.version === 1 &&\n \"storedAt\" in value &&\n typeof value.storedAt === \"number\" &&\n // Freshness uses the call-site strategy, but stored envelopes should still\n // look like the current shape so older or malformed entries do not become hits.\n \"strategy\" in value &&\n Boolean(value.strategy) &&\n \"value\" in value &&\n isSerializableCacheValue(value.value)\n );\n}\n\nfunction isSerializableCacheValue(value: unknown): value is SerializableCacheValue {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\n"],"mappings":";;;AAoDA,SAAgB,2BACd,OACsB;AACtB,KAAI,eAAe,MAAM,CAAE,QAAO,oBAAoB,MAAM;AAC5D,KAAI,oBAAoB,MAAM,CAAE,QAAO,yBAAyB,MAAM;AAEtE,OAAM,IAAI,UAAU,wEAAwE;;AAG9F,SAAS,oBAAoB,OAA2C;AACtE,QAAO;EACL,MAAM,IAAsC,KAAa;GACvD,MAAM,WAAW,MAAM,MAAM,MAAM,mBAAmB,IAAI,CAAC;AAC3D,OAAI,CAAC,SAAU,QAAO;GAEtB,MAAM,WAAoB,KAAK,MAAM,MAAM,SAAS,MAAM,CAAC;AAC3D,UAAO,gBAAmB,SAAS,GAAG,WAAW;;EAGnD,MAAM,IACJ,KACA,UACA,EAAE,YACF;GACA,MAAM,WAAW,IAAI,SAAS,KAAK,UAAU,SAAS,EAAE,EACtD,SAAS;IACP,iBAAiB,8BAA8B,SAAS;IACxD,gBAAgB;IACjB,EACF,CAAC;AAEF,SAAM,MAAM,IAAI,mBAAmB,IAAI,EAAE,SAAS;;EAGpD,MAAM,OAAO,KAAK;AAChB,SAAM,MAAM,OAAO,mBAAmB,IAAI,CAAC;;EAE9C;;AAGH,SAAS,yBAAyB,OAAgD;AAChF,QAAO;EACL,MAAM,IAAsC,KAAa;GACvD,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;AACrC,UAAO,gBAAmB,SAAS,GAAG,WAAW;;EAGnD,MAAM,IACJ,KACA,UACA,EAAE,YACF;GACA,MAAM,MAAM,KAAK,KAAK,qBAAqB,SAAS,CAAC;AAErD,SAAM,MAAM,IAAI,KAAK,UAAU,EAAE,KAAK,CAAC;;EAGzC,MAAM,OAAO,KAAK;AAChB,SAAM,MAAM,SAAS,IAAI;;EAE5B;;AAGH,SAAS,mBAAmB,KAAa;AACvC,QAAO,IAAI,QAAQ,0BAA0B,MAAM;;AAGrD,SAAS,eAAe,OAAuC;AAC7D,QACE,OAAO,UAAU,YACjB,SAAS,QACT,WAAW,SACX,SAAS,SACT,OAAO,MAAM,UAAU,cACvB,OAAO,MAAM,QAAQ;;AAIzB,SAAS,oBAAoB,OAA4C;AACvE,QACE,OAAO,UAAU,YACjB,SAAS,QACT,SAAS,SACT,SAAS,SACT,OAAO,MAAM,QAAQ,cACrB,OAAO,MAAM,QAAQ;;AAIzB,SAAS,gBACP,OAC2B;AAC3B,QACE,OAAO,UAAU,YACjB,SAAS,QACT,aAAa,SACb,MAAM,YAAY,KAClB,cAAc,SACd,OAAO,MAAM,aAAa,YAG1B,cAAc,SACd,QAAQ,MAAM,SAAS,IACvB,WAAW,SACX,yBAAyB,MAAM,MAAM;;AAIzC,SAAS,yBAAyB,OAAiD;AACjF,QAAO,OAAO,UAAU,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,MAAM"}
@@ -2,29 +2,54 @@
2
2
  declare const NO_STORE = "no-store";
3
3
  declare const PUBLIC = "public";
4
4
  declare const PRIVATE = "private";
5
+ /** The cache mode of a strategy, `public`, `private`, or `no-store`. A `no-store` strategy skips the cache. */
5
6
  type CacheMode = typeof PUBLIC | typeof PRIVATE | typeof NO_STORE;
7
+ /** The modes that a custom strategy accepts, `public` or `private`. `Cache` throws an error for any other mode. */
6
8
  type ExpirableCacheMode = typeof PUBLIC | typeof PRIVATE;
9
+ /**
10
+ * A number of seconds, or an object such as `{ minutes: 10 }`. `Cache` throws an error for a duration that isn't a finite, non-negative number, and rounds fractional totals up to whole seconds.
11
+ */
7
12
  type CacheDuration = number | {
8
13
  seconds?: number;
9
14
  minutes?: number;
10
15
  hours?: number;
11
16
  days?: number;
12
17
  };
18
+ /** A caching strategy, with every duration in whole seconds. Create a strategy with `Cache`. */
13
19
  interface CachingStrategy {
20
+ /** The cache mode. A `no-store` strategy skips the cache. */
14
21
  mode?: CacheMode;
22
+ /** How many seconds an entry stays fresh. */
15
23
  maxAge?: number;
24
+ /** How many seconds after the fresh window the cache serves a stale entry while it refreshes the entry in the background. */
16
25
  staleWhileRevalidate?: number;
26
+ /** How many seconds after the stale window the cache serves a stale entry when the refresh fails. */
17
27
  staleIfError?: number;
18
28
  }
29
+ /**
30
+ * Options for a custom caching strategy. A duration that you leave out counts as zero seconds.
31
+ *
32
+ * @publicDocs
33
+ */
19
34
  type CacheOptions = {
20
- mode?: ExpirableCacheMode;
21
- maxAge?: CacheDuration;
35
+ /** The cache mode, `public` or `private`. Defaults to `public`. Web Cache stores receive the mode in the `cache-control` header. The Storefront API client throws an error for `private`. */mode?: ExpirableCacheMode; /** How long an entry stays fresh. */
36
+ maxAge?: CacheDuration; /** How long after the fresh window the cache serves a stale entry while it refreshes the entry in the background. */
22
37
  staleWhileRevalidate?: CacheDuration;
38
+ /**
39
+ * How long after the stale-while-revalidate window the cache serves a stale entry when a refresh fails.
40
+ */
23
41
  staleIfError?: CacheDuration;
24
42
  };
43
+ /** A strategy that skips the cache. */
25
44
  type NoStoreStrategy = CachingStrategy & {
26
45
  mode: typeof NO_STORE;
27
46
  };
47
+ /**
48
+ * Creates caching strategies for Storefront API queries, cached fetches, and cached runs. Call
49
+ * `Cache` with options for a custom strategy, or use the `none`, `short`, and `long` presets.
50
+ *
51
+ * @publicDocs
52
+ */
28
53
  declare const Cache: typeof createCache & {
29
54
  none: typeof cacheNone;
30
55
  short: typeof cacheShort;
@@ -2,6 +2,12 @@
2
2
  const NO_STORE = "no-store";
3
3
  const PUBLIC = "public";
4
4
  const PRIVATE = "private";
5
+ /**
6
+ * Creates caching strategies for Storefront API queries, cached fetches, and cached runs. Call
7
+ * `Cache` with options for a custom strategy, or use the `none`, `short`, and `long` presets.
8
+ *
9
+ * @publicDocs
10
+ */
5
11
  const Cache = Object.assign(createCache, {
6
12
  none: cacheNone,
7
13
  short: cacheShort,