@vtex/faststore 0.0.7 → 0.0.10

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 (498) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/actions.json +194 -1
  3. package/dist/catalog.json +3449 -1612
  4. package/dist/cli/index.d.ts +512 -63
  5. package/dist/cli/index.js +2098 -686
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/server-runtime.js +44 -24
  8. package/dist/cli/server-runtime.js.map +1 -1
  9. package/dist/client.d.ts +5 -1
  10. package/dist/client.js +5 -1
  11. package/dist/components.d.ts +42 -10
  12. package/dist/components.js +47 -15
  13. package/dist/content-types.json +823 -0
  14. package/dist/core/dist/define-component.d.ts +12 -2
  15. package/dist/docs/account-access-gate.md +4 -4
  16. package/dist/docs/account-address-book.md +519 -0
  17. package/dist/docs/account-entry-point.md +5 -5
  18. package/dist/docs/account-menu.md +7 -7
  19. package/dist/docs/account-saved-cards.md +301 -0
  20. package/dist/docs/alert-dialog.md +2 -2
  21. package/dist/docs/billing-address.md +17 -9
  22. package/dist/docs/binding-switcher.md +154 -0
  23. package/dist/docs/card-entry.md +38 -7
  24. package/dist/docs/card-summary.md +175 -0
  25. package/dist/docs/cardholder-document.md +16 -14
  26. package/dist/docs/cart-destination.md +238 -0
  27. package/dist/docs/cart-item.md +22 -4
  28. package/dist/docs/cart-totalizer.md +1 -1
  29. package/dist/docs/cart.md +126 -19
  30. package/dist/docs/client-profile-fieldset.md +7 -5
  31. package/dist/docs/copy-text.md +84 -0
  32. package/dist/docs/document-field.md +8 -8
  33. package/dist/docs/footer-link-group.md +2 -2
  34. package/dist/docs/footer-social.md +1 -1
  35. package/dist/docs/free-shipping-progress.md +13 -9
  36. package/dist/docs/header-root.md +1 -1
  37. package/dist/docs/incentives.md +4 -4
  38. package/dist/docs/logo.md +6 -6
  39. package/dist/docs/my-account-profile.md +4 -4
  40. package/dist/docs/navigation-link-list.md +2 -2
  41. package/dist/docs/navigation-link.md +3 -3
  42. package/dist/docs/order-cancel.md +52 -0
  43. package/dist/docs/order-delivery-address.md +63 -0
  44. package/dist/docs/order-delivery-group.md +83 -0
  45. package/dist/docs/order-details-back.md +41 -0
  46. package/dist/docs/order-details-header.md +48 -0
  47. package/dist/docs/order-list-row.md +46 -0
  48. package/dist/docs/order-list-search.md +101 -0
  49. package/dist/docs/order-list-views.md +97 -0
  50. package/dist/docs/order-list.md +94 -0
  51. package/dist/docs/order-payment.md +94 -0
  52. package/dist/docs/order-placed-date.md +49 -0
  53. package/dist/docs/order-placed-payment-summary.md +13 -15
  54. package/dist/docs/order-placed.md +225 -0
  55. package/dist/docs/order-progress.md +88 -0
  56. package/dist/docs/order-status-badge.md +91 -0
  57. package/dist/docs/order-summary.md +53 -0
  58. package/dist/docs/payment-method-badges.md +16 -5
  59. package/dist/docs/payment-methods.md +1 -1
  60. package/dist/docs/place-order.md +13 -9
  61. package/dist/docs/product-information.md +2 -2
  62. package/dist/docs/product-shelf.md +7 -0
  63. package/dist/docs/saved-cards.md +2 -2
  64. package/dist/docs/select.md +22 -10
  65. package/dist/docs/session.md +1 -1
  66. package/dist/docs/shipping-address.md +1 -1
  67. package/dist/docs/sign-in-redirect.md +7 -7
  68. package/dist/docs/toast.md +35 -13
  69. package/dist/index.d.ts +7 -2
  70. package/dist/index.js +2 -1
  71. package/dist/internal/client.d.ts +9 -1
  72. package/dist/internal/client.js +10 -2
  73. package/dist/internal/server.d.ts +8 -2
  74. package/dist/internal/server.js +8 -2
  75. package/dist/matter/account-access-gate/account-access-gate.d.ts +2 -1
  76. package/dist/matter/account-access-gate/account-access-gate.js +19 -16
  77. package/dist/matter/account-access-gate/account-access-gate.js.map +1 -1
  78. package/dist/matter/account-address-book/account-address-book-delete.d.ts +60 -0
  79. package/dist/matter/account-address-book/account-address-book-delete.js +184 -0
  80. package/dist/matter/account-address-book/account-address-book-delete.js.map +1 -0
  81. package/dist/matter/account-address-book/account-address-book-deliverability.d.ts +19 -0
  82. package/dist/matter/account-address-book/account-address-book-deliverability.js +50 -0
  83. package/dist/matter/account-address-book/account-address-book-deliverability.js.map +1 -0
  84. package/dist/matter/account-address-book/account-address-book-form-parts.d.ts +42 -0
  85. package/dist/matter/account-address-book/account-address-book-form-parts.js +146 -0
  86. package/dist/matter/account-address-book/account-address-book-form-parts.js.map +1 -0
  87. package/dist/matter/account-address-book/account-address-book-profile-notice.d.ts +25 -0
  88. package/dist/matter/account-address-book/account-address-book-profile-notice.js +72 -0
  89. package/dist/matter/account-address-book/account-address-book-profile-notice.js.map +1 -0
  90. package/dist/matter/account-address-book/account-address-book.d.ts +120 -0
  91. package/dist/matter/account-address-book/account-address-book.js +469 -0
  92. package/dist/matter/account-address-book/account-address-book.js.map +1 -0
  93. package/dist/matter/account-address-book/account-address-country-switch.js +59 -0
  94. package/dist/matter/account-address-book/account-address-country-switch.js.map +1 -0
  95. package/dist/matter/account-address-book/account-address-country.d.ts +36 -0
  96. package/dist/matter/account-address-book/account-address-country.js +151 -0
  97. package/dist/matter/account-address-book/account-address-country.js.map +1 -0
  98. package/dist/matter/account-address-book/account-address-first-line.js +11 -0
  99. package/dist/matter/account-address-book/account-address-first-line.js.map +1 -0
  100. package/dist/matter/account-address-book/account-address-form-context.d.ts +55 -0
  101. package/dist/matter/account-address-book/account-address-form-context.js +14 -0
  102. package/dist/matter/account-address-book/account-address-form-context.js.map +1 -0
  103. package/dist/matter/account-address-book/account-address-form.d.ts +85 -0
  104. package/dist/matter/account-address-book/account-address-form.js +946 -0
  105. package/dist/matter/account-address-book/account-address-form.js.map +1 -0
  106. package/dist/matter/account-address-book/account-address-postal-autofill.js +189 -0
  107. package/dist/matter/account-address-book/account-address-postal-autofill.js.map +1 -0
  108. package/dist/matter/account-address-book/account-address-record.d.ts +26 -0
  109. package/dist/matter/account-address-book/account-address-record.js +138 -0
  110. package/dist/matter/account-address-book/account-address-record.js.map +1 -0
  111. package/dist/matter/account-entry-point/account-entry-point.js +11 -11
  112. package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
  113. package/dist/matter/account-menu/account-menu.d.ts +7 -3
  114. package/dist/matter/account-menu/account-menu.js +69 -28
  115. package/dist/matter/account-menu/account-menu.js.map +1 -1
  116. package/dist/matter/account-saved-cards/account-saved-cards.d.ts +383 -0
  117. package/dist/matter/account-saved-cards/account-saved-cards.js +956 -0
  118. package/dist/matter/account-saved-cards/account-saved-cards.js.map +1 -0
  119. package/dist/matter/billing-address/billing-address.d.ts +30 -17
  120. package/dist/matter/billing-address/billing-address.js +128 -33
  121. package/dist/matter/billing-address/billing-address.js.map +1 -1
  122. package/dist/matter/breadcrumb/breadcrumb.js +2 -2
  123. package/dist/matter/breadcrumb/breadcrumb.js.map +1 -1
  124. package/dist/matter/card-entry/card-capture.js +4 -3
  125. package/dist/matter/card-entry/card-capture.js.map +1 -1
  126. package/dist/matter/card-entry/card-entry.d.ts +5 -3
  127. package/dist/matter/card-entry/card-entry.js +77 -27
  128. package/dist/matter/card-entry/card-entry.js.map +1 -1
  129. package/dist/matter/cardholder-document/cardholder-document.d.ts +10 -9
  130. package/dist/matter/cardholder-document/cardholder-document.js +36 -16
  131. package/dist/matter/cardholder-document/cardholder-document.js.map +1 -1
  132. package/dist/matter/cart/cart-namespace.d.ts +5 -1
  133. package/dist/matter/cart/cart-namespace.js +5 -1
  134. package/dist/matter/cart/cart-namespace.js.map +1 -1
  135. package/dist/matter/cart/cart.d.ts +41 -6
  136. package/dist/matter/cart/cart.js +267 -54
  137. package/dist/matter/cart/cart.js.map +1 -1
  138. package/dist/matter/cart-destination/cart-destination.d.ts +222 -0
  139. package/dist/matter/cart-destination/cart-destination.js +613 -0
  140. package/dist/matter/cart-destination/cart-destination.js.map +1 -0
  141. package/dist/matter/cart-item/cart-item.d.ts +4 -2
  142. package/dist/matter/cart-item/cart-item.js +76 -17
  143. package/dist/matter/cart-item/cart-item.js.map +1 -1
  144. package/dist/matter/cart-totalizer/cart-totalizer.js +3 -2
  145. package/dist/matter/cart-totalizer/cart-totalizer.js.map +1 -1
  146. package/dist/matter/client-preferences/client-preferences-fieldset.js +1 -1
  147. package/dist/matter/client-profile-form/client-profile-fieldset.js +8 -5
  148. package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -1
  149. package/dist/matter/free-shipping-progress/free-shipping-progress.d.ts +6 -3
  150. package/dist/matter/free-shipping-progress/free-shipping-progress.js +76 -33
  151. package/dist/matter/free-shipping-progress/free-shipping-progress.js.map +1 -1
  152. package/dist/matter/identity-edit/identity-edit.js +3 -3
  153. package/dist/matter/identity-edit/identity-edit.js.map +1 -1
  154. package/dist/matter/identity-summary/identity-summary.js +2 -2
  155. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  156. package/dist/matter/my-account-profile/my-account-profile.d.ts +3 -3
  157. package/dist/matter/my-account-profile/my-account-profile.js +4 -4
  158. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
  159. package/dist/matter/order-cancel/order-cancel.d.ts +110 -0
  160. package/dist/matter/order-cancel/order-cancel.js +344 -0
  161. package/dist/matter/order-cancel/order-cancel.js.map +1 -0
  162. package/dist/matter/order-delivery-address/order-delivery-address.d.ts +73 -0
  163. package/dist/matter/order-delivery-address/order-delivery-address.js +172 -0
  164. package/dist/matter/order-delivery-address/order-delivery-address.js.map +1 -0
  165. package/dist/matter/order-delivery-group/order-delivery-group.d.ts +128 -0
  166. package/dist/matter/order-delivery-group/order-delivery-group.js +408 -0
  167. package/dist/matter/order-delivery-group/order-delivery-group.js.map +1 -0
  168. package/dist/matter/order-details-header/order-details-header.d.ts +104 -0
  169. package/dist/matter/order-details-header/order-details-header.js +190 -0
  170. package/dist/matter/order-details-header/order-details-header.js.map +1 -0
  171. package/dist/matter/order-list/customer-orders-filter.d.ts +12 -0
  172. package/dist/matter/order-list/customer-orders-filter.js +30 -0
  173. package/dist/matter/order-list/customer-orders-filter.js.map +1 -0
  174. package/dist/matter/order-list/order-list.d.ts +121 -0
  175. package/dist/matter/order-list/order-list.js +395 -0
  176. package/dist/matter/order-list/order-list.js.map +1 -0
  177. package/dist/matter/order-list-row/order-list-row.d.ts +44 -0
  178. package/dist/matter/order-list-row/order-list-row.js +127 -0
  179. package/dist/matter/order-list-row/order-list-row.js.map +1 -0
  180. package/dist/matter/order-list-search/order-list-search.d.ts +72 -0
  181. package/dist/matter/order-list-search/order-list-search.js +296 -0
  182. package/dist/matter/order-list-search/order-list-search.js.map +1 -0
  183. package/dist/matter/order-list-views/order-list-views.d.ts +65 -0
  184. package/dist/matter/order-list-views/order-list-views.js +154 -0
  185. package/dist/matter/order-list-views/order-list-views.js.map +1 -0
  186. package/dist/matter/order-payment/order-payment.d.ts +83 -0
  187. package/dist/matter/order-payment/order-payment.js +258 -0
  188. package/dist/matter/order-payment/order-payment.js.map +1 -0
  189. package/dist/matter/order-placed/order-placed-deliveries.d.ts +145 -0
  190. package/dist/matter/order-placed/order-placed-deliveries.js +380 -0
  191. package/dist/matter/order-placed/order-placed-deliveries.js.map +1 -0
  192. package/dist/matter/order-placed/order-placed.d.ts +122 -0
  193. package/dist/matter/order-placed/order-placed.js +452 -0
  194. package/dist/matter/order-placed/order-placed.js.map +1 -0
  195. package/dist/matter/order-placed-date/order-placed-date.d.ts +45 -0
  196. package/dist/matter/order-placed-date/order-placed-date.js +92 -0
  197. package/dist/matter/order-placed-date/order-placed-date.js.map +1 -0
  198. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +5 -8
  199. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +29 -81
  200. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -1
  201. package/dist/matter/order-progress/order-progress.d.ts +85 -0
  202. package/dist/matter/order-progress/order-progress.js +203 -0
  203. package/dist/matter/order-progress/order-progress.js.map +1 -0
  204. package/dist/matter/order-summary/order-summary.d.ts +81 -0
  205. package/dist/matter/order-summary/order-summary.js +192 -0
  206. package/dist/matter/order-summary/order-summary.js.map +1 -0
  207. package/dist/matter/payment/cardholder-document-rule.d.ts +35 -0
  208. package/dist/matter/payment/cardholder-document-rule.js +35 -0
  209. package/dist/matter/payment/cardholder-document-rule.js.map +1 -0
  210. package/dist/matter/payment/payment-card-capture.js +38 -23
  211. package/dist/matter/payment/payment-card-capture.js.map +1 -1
  212. package/dist/matter/payment/payment-controller.js +11 -0
  213. package/dist/matter/payment/payment-controller.js.map +1 -1
  214. package/dist/matter/payment/payment-method-badges.js +4 -2
  215. package/dist/matter/payment/payment-method-badges.js.map +1 -1
  216. package/dist/matter/payment/payment.js +11 -0
  217. package/dist/matter/payment/payment.js.map +1 -1
  218. package/dist/matter/payment/use-payment-method-selection.d.ts +39 -0
  219. package/dist/matter/payment/use-payment-method-selection.js +30 -2
  220. package/dist/matter/payment/use-payment-method-selection.js.map +1 -1
  221. package/dist/matter/payment/use-payment-submit-gate.d.ts +16 -2
  222. package/dist/matter/payment/use-payment-submit-gate.js +42 -14
  223. package/dist/matter/payment/use-payment-submit-gate.js.map +1 -1
  224. package/dist/matter/place-order/place-order.d.ts +12 -7
  225. package/dist/matter/place-order/place-order.js +126 -103
  226. package/dist/matter/place-order/place-order.js.map +1 -1
  227. package/dist/matter/pricing/pricing.js +5 -5
  228. package/dist/matter/pricing/pricing.js.map +1 -1
  229. package/dist/matter/product-shelf/product-shelf.d.ts +7 -0
  230. package/dist/matter/product-shelf/product-shelf.js +115 -1
  231. package/dist/matter/product-shelf/product-shelf.js.map +1 -1
  232. package/dist/matter/region/region.js +1 -1
  233. package/dist/matter/saved-cards/saved-cards.js +26 -15
  234. package/dist/matter/saved-cards/saved-cards.js.map +1 -1
  235. package/dist/matter/session/session.js +2 -2
  236. package/dist/matter/session/session.js.map +1 -1
  237. package/dist/matter/shipping-address/country-descriptors.d.ts +1 -0
  238. package/dist/matter/shipping-address/country-descriptors.js +23 -1
  239. package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
  240. package/dist/matter/shipping-address/shipping-address.js +62 -14
  241. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  242. package/dist/matter/shipping-simulator/shipping-simulator.d.ts +2 -2
  243. package/dist/matter/shipping-simulator/shipping-simulator.js +6 -2
  244. package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
  245. package/dist/matter/sign-in-redirect/sign-in-redirect.js +48 -10
  246. package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -1
  247. package/dist/matter/variant-selector/variant-selector.js +2 -2
  248. package/dist/matter/variant-selector/variant-selector.js.map +1 -1
  249. package/dist/particles/address-summary/address-summary.js +2 -2
  250. package/dist/particles/address-summary/address-summary.js.map +1 -1
  251. package/dist/particles/alert-dialog/alert-dialog.d.ts +5 -1
  252. package/dist/particles/alert-dialog/alert-dialog.js +5 -2
  253. package/dist/particles/alert-dialog/alert-dialog.js.map +1 -1
  254. package/dist/particles/binding-switcher/binding-switcher.d.ts +38 -0
  255. package/dist/particles/binding-switcher/binding-switcher.js +183 -0
  256. package/dist/particles/binding-switcher/binding-switcher.js.map +1 -0
  257. package/dist/particles/card-summary/card-network.d.ts +17 -0
  258. package/dist/particles/card-summary/card-network.js +25 -0
  259. package/dist/particles/card-summary/card-network.js.map +1 -0
  260. package/dist/particles/card-summary/card-summary.d.ts +83 -0
  261. package/dist/particles/card-summary/card-summary.js +188 -0
  262. package/dist/particles/card-summary/card-summary.js.map +1 -0
  263. package/dist/particles/copy-text/copy-text.d.ts +28 -0
  264. package/dist/particles/copy-text/copy-text.js +140 -0
  265. package/dist/particles/copy-text/copy-text.js.map +1 -0
  266. package/dist/particles/document-field/document-field.d.ts +34 -5
  267. package/dist/particles/document-field/document-field.js +115 -19
  268. package/dist/particles/document-field/document-field.js.map +1 -1
  269. package/dist/particles/field/field.js +1 -1
  270. package/dist/particles/field/field.js.map +1 -1
  271. package/dist/particles/footer-link-group/footer-link-group.d.ts +6 -2
  272. package/dist/particles/footer-link-group/footer-link-group.js +18 -7
  273. package/dist/particles/footer-link-group/footer-link-group.js.map +1 -1
  274. package/dist/particles/footer-social/footer-social.d.ts +1 -1
  275. package/dist/particles/footer-social/footer-social.js +1 -1
  276. package/dist/particles/footer-social/footer-social.js.map +1 -1
  277. package/dist/particles/incentives/incentives.d.ts +7 -3
  278. package/dist/particles/incentives/incentives.js +8 -3
  279. package/dist/particles/incentives/incentives.js.map +1 -1
  280. package/dist/particles/logo/logo.d.ts +3 -2
  281. package/dist/particles/logo/logo.js +20 -8
  282. package/dist/particles/logo/logo.js.map +1 -1
  283. package/dist/particles/navigation-link/navigation-link.d.ts +2 -3
  284. package/dist/particles/navigation-link/navigation-link.js +4 -4
  285. package/dist/particles/navigation-link/navigation-link.js.map +1 -1
  286. package/dist/particles/navigation-link-list/navigation-link-list.d.ts +6 -2
  287. package/dist/particles/navigation-link-list/navigation-link-list.js +18 -7
  288. package/dist/particles/navigation-link-list/navigation-link-list.js.map +1 -1
  289. package/dist/particles/order-details-back/order-details-back.d.ts +28 -0
  290. package/dist/particles/order-details-back/order-details-back.js +52 -0
  291. package/dist/particles/order-details-back/order-details-back.js.map +1 -0
  292. package/dist/particles/order-status-badge/order-status-badge.d.ts +19 -0
  293. package/dist/particles/order-status-badge/order-status-badge.js +50 -0
  294. package/dist/particles/order-status-badge/order-status-badge.js.map +1 -0
  295. package/dist/particles/payment-methods/payment-methods.d.ts +1 -1
  296. package/dist/particles/payment-methods/payment-methods.js +1 -1
  297. package/dist/particles/payment-methods/payment-methods.js.map +1 -1
  298. package/dist/particles/phone-field/phone-field.js +2 -2
  299. package/dist/particles/phone-field/phone-field.js.map +1 -1
  300. package/dist/particles/select/select.js +6 -6
  301. package/dist/particles/select/select.js.map +1 -1
  302. package/dist/particles/toast/toast.d.ts +21 -7
  303. package/dist/particles/toast/toast.js +76 -8
  304. package/dist/particles/toast/toast.js.map +1 -1
  305. package/dist/schema.graphql +1376 -126
  306. package/dist/sdk/actions/address-action-provider.d.ts +16 -0
  307. package/dist/sdk/actions/address-action-provider.js +20 -0
  308. package/dist/sdk/actions/address-action-provider.js.map +1 -0
  309. package/dist/sdk/actions/analytics-action-provider.js +1 -1
  310. package/dist/sdk/actions/index.d.ts +5 -1
  311. package/dist/sdk/actions/index.js +5 -1
  312. package/dist/sdk/actions/locale-action-provider.d.ts +16 -0
  313. package/dist/sdk/actions/locale-action-provider.js +20 -0
  314. package/dist/sdk/actions/locale-action-provider.js.map +1 -0
  315. package/dist/sdk/actions/order-action-provider.d.ts +12 -0
  316. package/dist/sdk/actions/order-action-provider.js +16 -0
  317. package/dist/sdk/actions/order-action-provider.js.map +1 -0
  318. package/dist/sdk/actions/saved-card-action-provider.d.ts +12 -0
  319. package/dist/sdk/actions/saved-card-action-provider.js +16 -0
  320. package/dist/sdk/actions/saved-card-action-provider.js.map +1 -0
  321. package/dist/sdk/actions/types.d.ts +10 -2
  322. package/dist/sdk/address/address-fields.js +38 -0
  323. package/dist/sdk/address/address-fields.js.map +1 -0
  324. package/dist/sdk/address/checkout-readiness.js +17 -0
  325. package/dist/sdk/address/checkout-readiness.js.map +1 -0
  326. package/dist/sdk/address/constants.js +60 -0
  327. package/dist/sdk/address/constants.js.map +1 -0
  328. package/dist/sdk/address/country-display.js +19 -0
  329. package/dist/sdk/address/country-display.js.map +1 -0
  330. package/dist/sdk/address/country-rules.js +30 -0
  331. package/dist/sdk/address/country-rules.js.map +1 -0
  332. package/dist/sdk/address/format-address.js +18 -0
  333. package/dist/sdk/address/format-address.js.map +1 -0
  334. package/dist/sdk/address/iso-countries.js +262 -0
  335. package/dist/sdk/address/iso-countries.js.map +1 -0
  336. package/dist/sdk/address/layouts.js +56 -0
  337. package/dist/sdk/address/layouts.js.map +1 -0
  338. package/dist/sdk/address/messages.js +97 -0
  339. package/dist/sdk/address/messages.js.map +1 -0
  340. package/dist/sdk/address/operations.d.ts +54 -0
  341. package/dist/sdk/address/operations.js +83 -0
  342. package/dist/sdk/address/operations.js.map +1 -0
  343. package/dist/sdk/address/postal-code-lookup-operations.js +23 -0
  344. package/dist/sdk/address/postal-code-lookup-operations.js.map +1 -0
  345. package/dist/sdk/address/postal-code.js +11 -0
  346. package/dist/sdk/address/postal-code.js.map +1 -0
  347. package/dist/sdk/address/schema.js +14 -0
  348. package/dist/sdk/address/schema.js.map +1 -0
  349. package/dist/sdk/address/server-fields.js +25 -0
  350. package/dist/sdk/address/server-fields.js.map +1 -0
  351. package/dist/sdk/address/switch-country.js +57 -0
  352. package/dist/sdk/address/switch-country.js.map +1 -0
  353. package/dist/sdk/address/types.d.ts +26 -0
  354. package/dist/sdk/address/use-address-actions.d.ts +29 -0
  355. package/dist/sdk/address/use-address-actions.js +247 -0
  356. package/dist/sdk/address/use-address-actions.js.map +1 -0
  357. package/dist/sdk/address/use-address-by-postal-code.js +57 -0
  358. package/dist/sdk/address/use-address-by-postal-code.js.map +1 -0
  359. package/dist/sdk/address/validate-address.js +62 -0
  360. package/dist/sdk/address/validate-address.js.map +1 -0
  361. package/dist/sdk/cache/storefront-cache.js +30 -4
  362. package/dist/sdk/cache/storefront-cache.js.map +1 -1
  363. package/dist/sdk/cart/operations.d.ts +2 -1
  364. package/dist/sdk/cart/operations.js +55 -1
  365. package/dist/sdk/cart/operations.js.map +1 -1
  366. package/dist/sdk/cart/optimistic.d.ts +30 -7
  367. package/dist/sdk/cart/optimistic.js +78 -41
  368. package/dist/sdk/cart/optimistic.js.map +1 -1
  369. package/dist/sdk/cart/use-cart-actions.d.ts +7 -0
  370. package/dist/sdk/cart/use-cart-actions.js +61 -15
  371. package/dist/sdk/cart/use-cart-actions.js.map +1 -1
  372. package/dist/sdk/cart/use-cart-sync-sales-channel.js +70 -0
  373. package/dist/sdk/cart/use-cart-sync-sales-channel.js.map +1 -0
  374. package/dist/sdk/client/fetch-page-data.d.ts +11 -0
  375. package/dist/sdk/client/fetch-page-data.js +12 -1
  376. package/dist/sdk/client/fetch-page-data.js.map +1 -1
  377. package/dist/sdk/content/create-content-runtime.js +1 -8
  378. package/dist/sdk/content/create-content-runtime.js.map +1 -1
  379. package/dist/sdk/data/failed-fields.js +23 -0
  380. package/dist/sdk/data/failed-fields.js.map +1 -0
  381. package/dist/sdk/data/index.js +2 -2
  382. package/dist/sdk/data/page-reactive-data-root.d.ts +45 -7
  383. package/dist/sdk/data/page-reactive-data-root.js +88 -12
  384. package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
  385. package/dist/sdk/data/reactive-list-item-data-provider.js +2 -2
  386. package/dist/sdk/generated/fragment-types.js +2 -0
  387. package/dist/sdk/generated/fragment-types.js.map +1 -1
  388. package/dist/sdk/generated/gql.js +17 -11
  389. package/dist/sdk/generated/gql.js.map +1 -1
  390. package/dist/sdk/generated/graphql.d.ts +185 -46
  391. package/dist/sdk/generated/graphql.js +2587 -155
  392. package/dist/sdk/generated/graphql.js.map +1 -1
  393. package/dist/sdk/i18n/messages/en-US.js +157 -1
  394. package/dist/sdk/i18n/messages/en-US.json +157 -1
  395. package/dist/sdk/i18n/messages/en-US.json.d.ts +156 -0
  396. package/dist/sdk/i18n/messages/es-ES.js +157 -1
  397. package/dist/sdk/i18n/messages/es-ES.json +157 -1
  398. package/dist/sdk/i18n/messages/pt-BR.js +157 -1
  399. package/dist/sdk/i18n/messages/pt-BR.json +157 -1
  400. package/dist/sdk/i18n/provider.js +1 -1
  401. package/dist/sdk/i18n/provider.js.map +1 -1
  402. package/dist/sdk/locale/binding-switch-provider.d.ts +13 -0
  403. package/dist/sdk/locale/binding-switch-provider.js +20 -0
  404. package/dist/sdk/locale/binding-switch-provider.js.map +1 -0
  405. package/dist/sdk/locale/use-locale-actions.d.ts +16 -0
  406. package/dist/sdk/locale/use-locale-actions.js +70 -0
  407. package/dist/sdk/locale/use-locale-actions.js.map +1 -0
  408. package/dist/sdk/navigation/binding-switch.d.ts +39 -0
  409. package/dist/sdk/navigation/binding-switch.js +129 -0
  410. package/dist/sdk/navigation/binding-switch.js.map +1 -0
  411. package/dist/sdk/navigation/current-page.js +30 -0
  412. package/dist/sdk/navigation/current-page.js.map +1 -0
  413. package/dist/sdk/navigation/decode-route-params.d.ts +11 -0
  414. package/dist/sdk/navigation/decode-route-params.js +32 -0
  415. package/dist/sdk/navigation/decode-route-params.js.map +1 -0
  416. package/dist/sdk/navigation/not-found-log.d.ts +28 -0
  417. package/dist/sdk/navigation/not-found-log.js +133 -0
  418. package/dist/sdk/navigation/not-found-log.js.map +1 -0
  419. package/dist/sdk/navigation/path.js +25 -0
  420. package/dist/sdk/navigation/path.js.map +1 -0
  421. package/dist/sdk/navigation/redirect.d.ts +11 -0
  422. package/dist/sdk/navigation/redirect.js +22 -0
  423. package/dist/sdk/navigation/redirect.js.map +1 -0
  424. package/dist/sdk/navigation/report.js +70 -0
  425. package/dist/sdk/navigation/report.js.map +1 -0
  426. package/dist/sdk/navigation/resolve-storefront-href.d.ts +12 -0
  427. package/dist/sdk/navigation/resolve-storefront-href.js +214 -0
  428. package/dist/sdk/navigation/resolve-storefront-href.js.map +1 -0
  429. package/dist/sdk/navigation/route-identity-provider.d.ts +11 -0
  430. package/dist/sdk/navigation/route-identity-provider.js +20 -0
  431. package/dist/sdk/navigation/route-identity-provider.js.map +1 -0
  432. package/dist/sdk/navigation/route-map-provider.d.ts +19 -0
  433. package/dist/sdk/navigation/route-map-provider.js +29 -0
  434. package/dist/sdk/navigation/route-map-provider.js.map +1 -0
  435. package/dist/sdk/navigation/storefront-link.d.ts +16 -0
  436. package/dist/sdk/navigation/storefront-link.js +66 -0
  437. package/dist/sdk/navigation/storefront-link.js.map +1 -0
  438. package/dist/sdk/navigation/types.d.ts +45 -0
  439. package/dist/sdk/navigation/use-storefront-navigation.d.ts +26 -0
  440. package/dist/sdk/navigation/use-storefront-navigation.js +73 -0
  441. package/dist/sdk/navigation/use-storefront-navigation.js.map +1 -0
  442. package/dist/sdk/navigation/use-storefront-route.d.ts +26 -0
  443. package/dist/sdk/navigation/use-storefront-route.js +87 -0
  444. package/dist/sdk/navigation/use-storefront-route.js.map +1 -0
  445. package/dist/sdk/navigation/use-storefront-search-params.d.ts +11 -0
  446. package/dist/sdk/navigation/use-storefront-search-params.js +16 -0
  447. package/dist/sdk/navigation/use-storefront-search-params.js.map +1 -0
  448. package/dist/sdk/orders/completion-badge.d.ts +8 -0
  449. package/dist/sdk/orders/completion-badge.js +47 -0
  450. package/dist/sdk/orders/completion-badge.js.map +1 -0
  451. package/dist/sdk/orders/order-operations.d.ts +19 -0
  452. package/dist/sdk/orders/order-operations.js +21 -0
  453. package/dist/sdk/orders/order-operations.js.map +1 -0
  454. package/dist/sdk/orders/use-order-actions.d.ts +23 -0
  455. package/dist/sdk/orders/use-order-actions.js +62 -0
  456. package/dist/sdk/orders/use-order-actions.js.map +1 -0
  457. package/dist/sdk/payment/gateway-origin.js +26 -13
  458. package/dist/sdk/payment/gateway-origin.js.map +1 -1
  459. package/dist/sdk/payment/gateway.d.ts +14 -2
  460. package/dist/sdk/payment/gateway.js +32 -14
  461. package/dist/sdk/payment/gateway.js.map +1 -1
  462. package/dist/sdk/payment/operations.js +11 -0
  463. package/dist/sdk/payment/operations.js.map +1 -1
  464. package/dist/sdk/payment/submission-registry.d.ts +2 -1
  465. package/dist/sdk/payment/submission-registry.js +12 -8
  466. package/dist/sdk/payment/submission-registry.js.map +1 -1
  467. package/dist/sdk/payment/use-payment-actions.d.ts +5 -3
  468. package/dist/sdk/payment/use-payment-actions.js +5 -2
  469. package/dist/sdk/payment/use-payment-actions.js.map +1 -1
  470. package/dist/sdk/payment/use-place-order.d.ts +6 -3
  471. package/dist/sdk/payment/use-place-order.js +333 -143
  472. package/dist/sdk/payment/use-place-order.js.map +1 -1
  473. package/dist/sdk/saved-cards/operations.d.ts +16 -0
  474. package/dist/sdk/saved-cards/operations.js +41 -0
  475. package/dist/sdk/saved-cards/operations.js.map +1 -0
  476. package/dist/sdk/saved-cards/use-saved-card-actions.d.ts +26 -0
  477. package/dist/sdk/saved-cards/use-saved-card-actions.js +86 -0
  478. package/dist/sdk/saved-cards/use-saved-card-actions.js.map +1 -0
  479. package/dist/sdk/seo/page-alternates.d.ts +57 -0
  480. package/dist/sdk/seo/page-alternates.js +127 -0
  481. package/dist/sdk/seo/page-alternates.js.map +1 -0
  482. package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
  483. package/dist/sdk/shipping/shipping-operations.js +13 -0
  484. package/dist/sdk/shipping/shipping-operations.js.map +1 -1
  485. package/dist/sdk/state/cart.d.ts +9 -2
  486. package/dist/sdk/state/cart.js +9 -2
  487. package/dist/sdk/state/cart.js.map +1 -1
  488. package/dist/utils/own-value.js +19 -0
  489. package/dist/utils/own-value.js.map +1 -0
  490. package/dist/utils/safe-href.d.ts +4 -4
  491. package/dist/utils/safe-href.js +31 -22
  492. package/dist/utils/safe-href.js.map +1 -1
  493. package/package.json +5 -1
  494. package/skills/storefront-asset/SKILL.md +111 -0
  495. package/skills/storefront-asset/references/api-side.md +153 -0
  496. package/skills/storefront-asset/references/findings.md +40 -0
  497. package/skills/storefront-asset/references/ui-side.md +94 -0
  498. package/src/styles/tailwind.css +28 -2
@@ -1 +1 @@
1
- {"version":3,"file":"create-content-runtime.js","names":[],"sources":["../../../src/sdk/content/create-content-runtime.tsx"],"sourcesContent":["import { createHmac, timingSafeEqual } from 'node:crypto';\nimport type { ContentPropertySchema, ContentTypeDefinition } from '@vtex/fs-core';\nimport { cookies, draftMode } from 'next/headers';\nimport { connection } from 'next/server';\nimport { cache, type FC, type ReactNode } from 'react';\n\nexport type ContentRuntimeConfig = {\n account: string;\n storeId: string;\n /** Canonical per-component models, verbatim from the aggregated content catalog, keyed by id. */\n contentTypes: Record<string, ContentTypeDefinition>;\n};\n\n/** The coordinates of one composite read. `locale` arrives per call, so one build serves many. */\nexport type ReadContentSlicesArgs = {\n contentTypeId: string;\n entryId: string;\n locale: string;\n /** `element.id → per-component model id` for every content-backed element in the scope. */\n elements: Record<string, string>;\n};\n\n/**\n * The scope's slices, keyed by `element.id`. A key is absent when its element must be omitted — a\n * missing key, a shape violation, or a whole-entry failure.\n *\n * The value type is unchecked on purpose: the generated page forwards it to a component whose\n * `$content` is `ContentValue<that component's model>`, a type that exists only in the component\n * package, and an index signature cannot satisfy a model's required properties. Stated once here\n * rather than cast at every emitted prop; the read below validates every value against its model.\n */\nexport type ContentSlices = {\n // biome-ignore lint/suspicious/noExplicitAny: the model's type lives in the component package, not here.\n [elementId: string]: any;\n};\n\nexport type ContentEntryBoundaryProps = {\n /** This element's slice, read at the top of the scope. `undefined` omits the component. */\n value: unknown;\n children: ReactNode;\n};\n\n/** The coordinates of one item read. Object arguments, so two strings cannot be swapped at the call site. */\nexport type ReadContentItemArgs = {\n contentTypeId: string;\n slug: string;\n locale: string;\n};\n\n/**\n * One item read. `found` is the resolved entry. `not-found` is HTTP 404 only. `unavailable` is every\n * other failure with nothing stored. There is no `invalid` arm: a 200 JSON object is the blob.\n */\nexport type ContentItemResult =\n | { status: 'found'; blob: Record<string, unknown> }\n | { status: 'not-found' }\n | { status: 'unavailable'; reason: string };\n\nexport type ContentRuntime = {\n readContentSlices: (args: ReadContentSlicesArgs) => Promise<ContentSlices>;\n readContentItem: (args: ReadContentItemArgs) => Promise<ContentItemResult>;\n ContentEntryBoundary: FC<ContentEntryBoundaryProps>;\n};\n\n/** The signed preview context the Draft Mode route stores; re-verified here before use. */\ntype PreviewClaims = {\n account: string;\n storeId: string;\n branch: string;\n /** Millisecond epoch the token was minted / expires; a captured cookie stops working past `exp`. */\n iat: number;\n exp: number;\n};\n\n/**\n * Internal result taxonomy: a read either finds a validated composite blob or fails for a definite,\n * loggable reason. A transient upstream failure resolves `found` from the last-good store below when\n * that store holds the entry, and degrades to `unavailable` — omitting the affected components —\n * only when it does not.\n */\nexport type ContentEntryResult =\n | { status: 'found'; blob: Record<string, unknown> }\n | { status: 'not-found' }\n | { status: 'unavailable'; reason: string }\n | { status: 'invalid'; reason: string };\n\n/**\n * One published read's outcome. `staleReason` is present only when the fresh read failed and the\n * store covered it, and the result is then an ordinary `found`: no consumer has a fifth renderable\n * status to learn, and only the log line tells a served blob from a fresh one.\n */\ntype PublishedRead = { result: ContentEntryResult; staleReason?: string };\n\n/** Same shape as `PublishedRead`, on the item taxonomy (no `invalid` arm). */\ntype PublishedItemRead = { result: ContentItemResult; staleReason?: string };\n\nconst ENTRY_REQUEST_TIMEOUT_MS = 5000;\n\n/**\n * Retention ceilings for the last-good store, both estimated rather than measured: no production\n * store exists yet to size a real composite against, and the example schemas in this repo are demo\n * content, not authored pages, so neither is evidence of real scale.\n *\n * `MAX_RETAINED_BLOB_BYTES` bounds one entry. Ordinary editorial text (a title, a body, an image URL,\n * an alt text) stays in the hundreds of bytes to a few kilobytes per element regardless of how many\n * elements a page composes — even the largest composed layout in this repo (103 elements) does not\n * read as megabytes if every element stays plain text. The realistic way to exceed this is a single\n * field holding embedded binary — a base64 image pasted into a text field instead of uploaded through\n * the media widget — which is exactly the shape this guards against, independent of element count.\n *\n * `MAX_RETAINED_TOTAL_BYTES` bounds the whole store, and is the one that actually matters: the\n * per-blob cap alone does not bound total memory, because it multiplies by key count, and key count is\n * not small — a large store's pages and layouts, times its locales, is plausibly a few hundred keys.\n * A few hundred keys at a few megabytes each already exceeds a typical serverless heap (512 MB–1 GB)\n * on the per-blob cap alone; ~64 MB is a small slice of that heap while still holding many ordinary\n * pages at once.\n *\n * Both numbers count serialized JSON text (`blobByteLength`), not retained heap: a parsed object\n * typically costs more than its JSON text — V8 pays per-property and per-string object overhead that\n * the text itself does not — so the real footprint at either ceiling is larger than the number on the\n * tin, plausibly by a small multiple. Both ceilings still leave headroom against a 512 MB–1 GB heap at\n * that multiple; sizing them as retained heap instead would need a runtime memory measurement this\n * module has no cheap way to take.\n *\n * Both numbers are round and generous on purpose, not derived. `content.retention.skipped` below is\n * the real calibration path: once a production store exists, its log volume says whether either\n * ceiling needs to move.\n */\nconst MAX_RETAINED_BLOB_BYTES = 1_000_000;\nconst MAX_RETAINED_TOTAL_BYTES = 64_000_000;\n\n/** Server-only preview credential, supplied at deployment time; never reaches the browser. */\nconst PREVIEW_TOKEN_ENV = 'CONTENT_PLATFORM_PREVIEW_TOKEN';\n/** Shared broker secret used to verify the signed preview context cookie. */\nconst PREVIEW_SECRET_ENV = 'CONTENT_PREVIEW_SECRET';\n/** The signed preview context cookie the Draft Mode route sets. */\nconst PREVIEW_COOKIE = '__fs_content_preview';\n\n/**\n * Creates the one server-side content read surface for a store: a `React.cache`-deduped\n * `readContentSlices` the generated page or layout awaits once at the top, and the\n * `ContentEntryBoundary` the compiler wraps around each content-backed element. One read per scope\n * yields every element's `$content` value keyed by `element.id`, so a page issues at most two Data\n * Plane reads (its own entry plus its layout's) regardless of content-backed component count. Draft\n * Mode switches that composite read to Control Plane's preview endpoint through a server-only\n * credential. `readContentItem` reads one entry by slug and does not consult Draft Mode.\n */\nexport function createContentRuntime(config: ContentRuntimeConfig): ContentRuntime {\n // The blob of the most recent successful published read, per entry, so a failing Data Plane\n // renders the last content that worked instead of holes. Not `React.cache`, whose lifetime is the\n // request and so shorter than the failure it has to cover, and not module scope, which every\n // runtime in the process would share — two of them on the same coordinates would then serve each\n // other's content. The generated consumer holds the runtime in a module-scope `const`, so the\n // closure lives as long as the process either way.\n //\n // `contentTypeId` and `entryId` are compiler-emitted literals for the life of the deployment (a\n // dynamic route gets one entry, not one per slug), so today's key domain is small: pages plus\n // layouts, times locales. That is an invariant of the generated caller, not of this function —\n // nothing here stops a future one from passing a request-derived value instead, and the moment it\n // does, this map grows with traffic rather than with the store's page count.\n //\n // Item reads share this map and both ceilings. Their key starts with `item` and a NUL, then the\n // content type, slug, and locale, so it cannot equal a composite key even when the slug equals an\n // entry id. A slug is request-derived, so that part of the map grows with distinct items.\n const lastGood = new Map<string, Record<string, unknown>>();\n let retainedBytes = 0;\n\n type RetainFields =\n | { contentTypeId: string; entryId: string; locale: string }\n | { contentTypeId: string; slug: string; locale: string };\n\n /**\n * Whether a successful read's blob replaces what `key` already holds, weighed against both\n * ceilings. Skipping never deletes: whatever `key` held before, if anything, is left exactly as it\n * was — only the update is declined, so a rare oversized read cannot erase a smaller last-good value\n * that a future failure could still use.\n */\n function retainBlob(key: string, blob: Record<string, unknown>, fields: RetainFields): void {\n const size = blobByteLength(blob);\n if (size > MAX_RETAINED_BLOB_BYTES) {\n logEvent('warn', 'content.retention.skipped', { ...fields, bytes: size, reason: 'blob-too-large' });\n return;\n }\n const existing = lastGood.get(key);\n const existingSize = existing ? blobByteLength(existing) : 0;\n if (retainedBytes - existingSize + size > MAX_RETAINED_TOTAL_BYTES) {\n logEvent('warn', 'content.retention.skipped', { ...fields, bytes: size, reason: 'total-cap-reached' });\n return;\n }\n lastGood.set(key, blob);\n retainedBytes += size - existingSize;\n }\n\n /**\n * The published read plus the fallback that covers it. The upstream is always called and the store\n * is read only in the `catch`, which is what makes this a fallback and not a cache: a publish is\n * visible on the very next request. A success ordinarily overwrites the entry (see `retainBlob` for\n * the two cases where it does not), a `not-found` leaves it alone so unpublishing still unpublishes,\n * and nothing is ever removed or expired by age.\n *\n * Failure is structural — whatever leaves `readPublishedEntry` as a throw — rather than a status\n * table, so a 403 or a 429 serves the stored blob, and a failure mode added there later is covered\n * without touching this.\n */\n async function readPublished(contentTypeId: string, entryId: string, locale: string): Promise<PublishedRead> {\n // NUL appears in no content-type id, entry id, or BCP-47 tag, so two distinct triples cannot\n // collide. Keyed on the same three values the memoisation below uses, so the two layers never\n // disagree about what \"the same read\" is.\n const key = `${contentTypeId}\\u0000${entryId}\\u0000${locale}`;\n try {\n const result = await readPublishedEntry(config.account, config.storeId, locale, contentTypeId, entryId);\n if (result.status === 'found') {\n retainBlob(key, result.blob, { contentTypeId, entryId, locale });\n }\n return { result };\n } catch (error) {\n const reason = transientReason(error);\n const stored = lastGood.get(key);\n // Nothing stored: omit the affected components while the rest of the route renders.\n if (stored === undefined) return { result: { status: 'unavailable', reason } };\n return { result: { status: 'found', blob: stored }, staleReason: reason };\n }\n }\n\n /**\n * The by-slug read plus the same fallback. Not a parameter of `readPublished`: the key, the URL,\n * and the result taxonomy differ, and folding them together would put a slug on the composite log\n * line. A `not-found` returns before the map is read or written.\n */\n async function readPublishedItem(contentTypeId: string, slug: string, locale: string): Promise<PublishedItemRead> {\n // The `item` prefix is what keeps this off `${contentTypeId}\\u0000${entryId}\\u0000${locale}`.\n const key = `item\\u0000${contentTypeId}\\u0000${slug}\\u0000${locale}`;\n try {\n const result = await readItemBySlug(config.account, config.storeId, locale, contentTypeId, slug);\n if (result.status === 'found') {\n retainBlob(key, result.blob, { contentTypeId, slug, locale });\n }\n return { result };\n } catch (error) {\n const reason = transientReason(error);\n const stored = lastGood.get(key);\n if (stored === undefined) return { result: { status: 'unavailable', reason } };\n return { result: { status: 'found', blob: stored }, staleReason: reason };\n }\n }\n\n // React.cache collapses same-entry awaits within one request: N boundaries reading the same\n // composite entry share one upstream read and one read-log line. That is per-request memoisation\n // only — nothing is reused across requests — so it is `readPublished` above, not this, that\n // carries a value across the failure it covers. Either way a read never throws a route error.\n const read = cache(async (contentTypeId: string, entryId: string, locale: string): Promise<ContentEntryResult> => {\n // Content is read per request, never at build time. Nothing else on a content-only page is\n // request-dependent — `draftMode()` prerenders as disabled and an uncached `fetch` does not bail\n // out on its own — so without this the entry is captured by a static prerender and the store\n // stops tracking the CMS until its next deploy. `'use cache'` used to hold the route open with a\n // five-minute revalidate; removing it for a platform that does not stream took that with it.\n // Outside the fallback's try, which lives in `readPublished`, so the bail-out it throws during\n // static generation is never swallowed as a transient failure.\n await connection();\n const { isEnabled: draft } = await draftMode();\n let result: ContentEntryResult;\n let staleReason: string | undefined;\n if (draft) {\n // The preview branch neither reads the store nor writes it: an unpublished blob must not\n // outlive the request that asked for it, and a draft must never cover a published failure.\n result = await readPreviewEntry(config, contentTypeId, entryId, locale);\n } else {\n ({ result, staleReason } = await readPublished(contentTypeId, entryId, locale));\n }\n // A served blob is `found`, so the status alone would report it as a success.\n const stale = staleReason !== undefined;\n const { level, event } = stale ? STALE_READ_EVENT : READ_EVENTS[result.status];\n const reason =\n staleReason ?? (result.status === 'unavailable' || result.status === 'invalid' ? result.reason : undefined);\n // The success line is the cheapest evidence of the ≤2-reads-per-page guarantee, and its only\n // cost is production volume: nothing caches this read, so it would emit once or twice per page\n // view and bury the rare failure line. Suppressed there, kept everywhere the count is checked —\n // and the gate covers a genuine success only, so a covered outage stays audible in production.\n const suppressed = !stale && result.status === 'found' && process.env.NODE_ENV === 'production';\n if (!suppressed) {\n logEvent(level, event, {\n contentTypeId,\n entryId,\n locale,\n source: draft ? 'control-plane' : 'data-plane',\n ...(reason === undefined ? {} : { reason }),\n });\n }\n return result;\n });\n\n /**\n * The scope's one read, awaited before render: the composite entry, sliced into one `$content`\n * value per content-backed element. The values reach the components as literal props, so they are\n * part of the server render by construction — a `cloneElement` from a client-side provider lands\n * only on hydration, which is what made server HTML and client DOM disagree.\n *\n * The public shape takes one object, so a call site cannot silently swap two same-typed positional\n * arguments; `React.cache` keys on the positional ones underneath.\n */\n async function readContentSlices({\n contentTypeId,\n entryId,\n locale,\n elements,\n }: ReadContentSlicesArgs): Promise<ContentSlices> {\n const result = await read(contentTypeId, entryId, locale);\n if (result.status !== 'found') {\n // A whole-entry failure (404, invalid, or an upstream failure with nothing stored for the\n // entry) omits every component sharing it: no key is present, so every boundary renders\n // nothing. A failure the last-good store covered never arrives here — it arrives as `found`.\n return emptySlices();\n }\n const slices = emptySlices();\n const requested = Object.entries(elements);\n const omissions: ElementOmission[] = [];\n for (const [elementId, modelId] of requested) {\n const slice = sliceContent(result.blob, elementId, ownValue(config.contentTypes, modelId));\n if (slice.status === 'omitted') {\n // A missing or invalid key within an otherwise-valid entry omits only this one component.\n omissions.push({ elementId, modelId, reason: slice.reason });\n continue;\n }\n slices[elementId] = slice.content;\n }\n if (omissions.length > 0) {\n // One line per call rather than one per element: the operational fact is the ratio, and\n // `omitted === total` is what tells a blank scope from a partial one. The level says only that\n // the page rendered, so it stays `warn` at every ratio.\n logEvent('warn', 'content.elements.omitted', {\n contentTypeId,\n entryId,\n locale,\n omitted: omissions.length,\n total: requested.length,\n omissions,\n });\n }\n return slices;\n }\n\n // A separate cache from `read`. Positional key is content type, slug, locale. `connection()` is\n // outside `readPublishedItem`'s try, so a prerender bail-out propagates unlogged. Draft Mode is\n // not a branch this read has: there is no slug preview route, and a Data Plane `branch` query is\n // ignored by the platform.\n const readItem = cache(async (contentTypeId: string, slug: string, locale: string): Promise<ContentItemResult> => {\n await connection();\n const { result, staleReason } = await readPublishedItem(contentTypeId, slug, locale);\n const stale = staleReason !== undefined;\n const { level, event } = stale ? STALE_READ_EVENT : READ_EVENTS[result.status];\n const reason = staleReason ?? (result.status === 'unavailable' ? result.reason : undefined);\n const suppressed = !stale && result.status === 'found' && process.env.NODE_ENV === 'production';\n if (!suppressed) {\n logEvent(level, event, {\n contentTypeId,\n slug,\n locale,\n source: 'data-plane',\n ...(reason === undefined ? {} : { reason }),\n });\n }\n return result;\n });\n\n async function readContentItem({ contentTypeId, slug, locale }: ReadContentItemArgs): Promise<ContentItemResult> {\n return readItem(contentTypeId, slug, locale);\n }\n\n return { readContentSlices, readContentItem, ContentEntryBoundary };\n}\n\n/**\n * The slice map, with no prototype. An element id is a schema string, so `__proto__` is a name the\n * map can be asked for; on a plain object the lookup would inherit `Object.prototype` and a boundary\n * would render an element whose `$content` is that object rather than omitting it. The failure path\n * returns this too — an entry that 404s must answer `undefined` for every key, including that one.\n */\nfunction emptySlices(): ContentSlices {\n return Object.create(null) as ContentSlices;\n}\n\n/**\n * Renders its element only when that element's slice is present — the omission rule the content RFC\n * states. It no longer delivers the value (the page passes `$content` as a literal prop), so it\n * clones nothing and what wraps it no longer decides whether content arrives.\n *\n * Module scope, not a closure over `createContentRuntime`: it reads nothing from the runtime's\n * configuration, so every store shares one component identity.\n */\nfunction ContentEntryBoundary({ value, children }: ContentEntryBoundaryProps): ReactNode {\n return value === undefined ? null : children;\n}\n\n/**\n * The published read: one composite entry from the Data Plane, live on every request. A definitive\n * 404 resolves a `not-found` sentinel; a transient failure (network, timeout, 5xx, malformed body)\n * throws, and `readPublished` decides between the stored blob and an omit.\n *\n * Nothing caches it. This read sat under `'use cache'` until Cache Components was removed for a\n * platform that does not stream, and the five-minute `cacheLife` and 24-hour `expire` went with it —\n * a transient failure used to throw inside the cache and Next kept serving the last-good entry for\n * up to a day. What replaced that is the last-good store, which is a fallback and not a cache: it\n * never shortens or skips this read, so the happy path is unchanged and a publish lands on the very\n * next request. The page cache, the two cache tags this read emitted and the tag-based revalidation\n * route they fed are still gone, and still owed by whatever restores caching.\n */\nasync function readPublishedEntry(\n account: string,\n storeId: string,\n locale: string,\n contentTypeId: string,\n entryId: string,\n): Promise<ContentEntryResult> {\n const url = buildDataPlaneUrl(account, storeId, locale, contentTypeId, entryId);\n const response = await fetch(url, { signal: AbortSignal.timeout(ENTRY_REQUEST_TIMEOUT_MS) });\n\n if (response.status === 404) return { status: 'not-found' };\n if (!response.ok) throw new Error(`content upstream status ${response.status}`);\n\n let payload: unknown;\n try {\n payload = await response.json();\n } catch {\n throw new Error('content response is not JSON');\n }\n if (!isRecord(payload)) throw new Error('content response is not an object');\n\n return { status: 'found', blob: payload };\n}\n\n/**\n * One entry from the Data Plane by slug. A 404 resolves `not-found` before `json()`. Any other\n * non-OK status, a non-JSON body, or a non-object body throws, and `readPublishedItem` decides\n * between the stored blob and `unavailable`. A 200 object is `found` with no field-schema check.\n * `locale_metadata` is the platform envelope (it carries an inline SVG) and this path has no slicer,\n * so that own property is removed before the blob is returned or retained. No other key is dropped.\n * A list body is not an object, so it throws rather than resolving `not-found`.\n */\nasync function readItemBySlug(\n account: string,\n storeId: string,\n locale: string,\n contentTypeId: string,\n slug: string,\n): Promise<ContentItemResult> {\n const url = buildDataPlaneSlugUrl(account, storeId, locale, contentTypeId, slug);\n const response = await fetch(url, { signal: AbortSignal.timeout(ENTRY_REQUEST_TIMEOUT_MS) });\n\n if (response.status === 404) return { status: 'not-found' };\n if (!response.ok) throw new Error(`content upstream status ${response.status}`);\n\n let payload: unknown;\n try {\n payload = await response.json();\n } catch {\n throw new Error('content response is not JSON');\n }\n if (!isRecord(payload)) throw new Error('content response is not an object');\n if (Object.hasOwn(payload, 'locale_metadata')) {\n delete payload.locale_metadata;\n }\n\n return { status: 'found', blob: payload };\n}\n\n/**\n * The Draft Mode read: the same composite entry resolved through Control Plane's preview endpoint\n * with a server-only credential, bypassing all caches. The branch comes from a SIGNED preview\n * context cookie whose HMAC is re-verified here against the broker secret — a tampered cookie fails\n * closed, so a valid session cannot read an unauthorized branch's drafts. The credential never\n * enters browser code, logs, URLs, or props. Uncached, so its sentinels are never persisted.\n */\nasync function readPreviewEntry(\n config: ContentRuntimeConfig,\n contentTypeId: string,\n entryId: string,\n locale: string,\n): Promise<ContentEntryResult> {\n const token = process.env[PREVIEW_TOKEN_ENV];\n if (!token) return { status: 'unavailable', reason: 'preview credential unavailable' };\n const secret = process.env[PREVIEW_SECRET_ENV];\n if (!secret) return { status: 'unavailable', reason: 'preview secret unavailable' };\n\n const store = await cookies();\n const raw = store.get(PREVIEW_COOKIE)?.value;\n const claims = raw ? verifyPreviewContext(raw, secret) : null;\n if (!claims || claims.account !== config.account || claims.storeId !== config.storeId || !claims.branch) {\n return { status: 'unavailable', reason: 'invalid preview context' };\n }\n\n const { account, storeId } = config;\n const path = [account, storeId, contentTypeId, 'entries', 'preview', entryId].map(encodeURIComponent).join('/');\n const url = `https://${account}.vtexcommercestable.com.br/api/content-platform/manage/${path}?branchId=${encodeURIComponent(claims.branch)}&locale=${encodeURIComponent(locale)}`;\n return fetchPreview(url, token);\n}\n\nasync function fetchPreview(url: string, token: string): Promise<ContentEntryResult> {\n let response: Response;\n try {\n response = await fetch(url, {\n cache: 'no-store',\n headers: { Authorization: `Bearer ${token}` },\n signal: AbortSignal.timeout(ENTRY_REQUEST_TIMEOUT_MS),\n });\n } catch (error) {\n return { status: 'unavailable', reason: isTimeoutError(error) ? 'timeout' : 'network error' };\n }\n if (response.status === 404) return { status: 'not-found' };\n if (!response.ok) return { status: 'unavailable', reason: `upstream status ${response.status}` };\n let payload: unknown;\n try {\n payload = await response.json();\n } catch {\n return { status: 'invalid', reason: 'response body is not JSON' };\n }\n if (!isRecord(payload)) return { status: 'invalid', reason: 'response body is not an object' };\n return { status: 'found', blob: payload };\n}\n\n/**\n * Verifies a signed preview context cookie (`base64url(claims).base64url(hmac)`) against the broker\n * secret with a constant-time comparison, returning the claims only when the signature holds and the\n * token has not expired — a captured cookie fails closed once past `exp`.\n */\nfunction verifyPreviewContext(raw: string, secret: string): PreviewClaims | null {\n const [body, signature] = raw.split('.');\n if (!body || !signature) return null;\n const expected = createHmac('sha256', secret).update(body).digest('base64url');\n const given = Buffer.from(signature, 'utf8');\n const want = Buffer.from(expected, 'utf8');\n if (given.length !== want.length || !timingSafeEqual(given, want)) return null;\n try {\n const claims = JSON.parse(Buffer.from(body, 'base64url').toString('utf8')) as PreviewClaims;\n if (typeof claims.branch !== 'string' || !Number.isFinite(claims.exp) || Date.now() >= claims.exp) {\n return null;\n }\n return claims;\n } catch {\n return null;\n }\n}\n\nfunction buildDataPlaneUrl(\n account: string,\n storeId: string,\n locale: string,\n contentTypeId: string,\n entryId: string,\n): string {\n const path = [account, storeId, contentTypeId, 'entries', entryId].map(encodeURIComponent).join('/');\n return `https://${account}.vtexcommercestable.com.br/api/content-platform/data/${path}?locale=${encodeURIComponent(locale)}`;\n}\n\n/** Same origin and `locale` query as `buildDataPlaneUrl`. The slug is one segment: it is not split on `/`. */\nfunction buildDataPlaneSlugUrl(\n account: string,\n storeId: string,\n locale: string,\n contentTypeId: string,\n slug: string,\n): string {\n const path = [account, storeId, contentTypeId, 'entries', 'slug', slug].map(encodeURIComponent).join('/');\n return `https://${account}.vtexcommercestable.com.br/api/content-platform/data/${path}?locale=${encodeURIComponent(locale)}`;\n}\n\ntype ContentSliceResult =\n | { status: 'sliced'; content: Record<string, unknown> }\n | { status: 'omitted'; reason: string };\n\n/**\n * Slices one element's value out of the composite blob by its `element.id` and validates it against\n * that element's per-component model. Omits only this component on a missing key, a non-object\n * value, an unknown model, or a shape violation — keyword constraints are the preflight's job.\n * Failing closed on an unknown model keeps an unvalidated blob from reaching `$content` (which would\n * crash the route as a React child).\n */\nfunction sliceContent(\n blob: Record<string, unknown>,\n elementId: string,\n model: ContentTypeDefinition | undefined,\n): ContentSliceResult {\n const raw = ownValue(blob, elementId);\n if (!isRecord(raw)) return { status: 'omitted', reason: 'missing or non-object value' };\n if (!model) return { status: 'omitted', reason: 'unknown model' };\n\n const content = projectObject(model.schema.properties, raw);\n const violation = findShapeViolation(model, content);\n if (violation !== null) return { status: 'omitted', reason: violation };\n return { status: 'sliced', content };\n}\n\n/** A closed object shape, at the root or one level down. */\ntype ClosedObjectShape = {\n required?: readonly string[];\n properties: Record<string, ContentPropertySchema>;\n};\n\n/**\n * Keeps declared keys only, at every level the vocabulary admits, so the injected value matches the\n * `ContentValue` type the component was compiled against. A value of the wrong shape passes through\n * untouched for the validator below to reject.\n */\nfunction projectObject(\n properties: Record<string, ContentPropertySchema>,\n raw: Record<string, unknown>,\n): Record<string, unknown> {\n const projected: Record<string, unknown> = {};\n for (const [key, property] of Object.entries(properties)) {\n const value = ownValue(raw, key);\n if (value === undefined) continue;\n if (property.type === 'array') {\n projected[key] = Array.isArray(value)\n ? value.map((member) => (isRecord(member) ? projectObject(property.items.properties, member) : member))\n : value;\n } else if (property.type === 'object') {\n projected[key] = isRecord(value) ? projectObject(property.properties, value) : value;\n } else {\n projected[key] = value;\n }\n }\n return projected;\n}\n\n/** Shape-level validation only (required presence, primitive type, enum membership), at every level. */\nfunction findShapeViolation(model: ContentTypeDefinition, content: Record<string, unknown>): string | null {\n return findObjectViolation('', model.schema, content);\n}\n\nfunction findObjectViolation(path: string, shape: ClosedObjectShape, value: Record<string, unknown>): string | null {\n for (const key of shape.required ?? []) {\n if (ownValue(value, key) === undefined) return `missing required property \"${joinPath(path, key)}\"`;\n }\n for (const [key, property] of Object.entries(shape.properties)) {\n const member = ownValue(value, key);\n if (member === undefined) continue;\n const violation = findPropertyViolation(joinPath(path, key), property, member);\n if (violation !== null) return violation;\n }\n return null;\n}\n\nfunction findPropertyViolation(path: string, property: ContentPropertySchema, value: unknown): string | null {\n if (property.type === 'array') {\n if (!Array.isArray(value)) return `property \"${path}\" does not match type \"array\"`;\n for (const [index, member] of value.entries()) {\n const itemPath = `${path}[${index}]`;\n if (!isRecord(member)) return `property \"${itemPath}\" does not match type \"object\"`;\n const violation = findObjectViolation(itemPath, property.items, member);\n if (violation !== null) return violation;\n }\n // minItems and maxItems are author-time bounds; the read path does not enforce them.\n return null;\n }\n if (property.type === 'object') {\n if (!isRecord(value)) return `property \"${path}\" does not match type \"object\"`;\n return findObjectViolation(path, property, value);\n }\n if (!matchesScalarType(property.type, value)) return `property \"${path}\" does not match type \"${property.type}\"`;\n if (property.enum && !property.enum.includes(value as string | number)) {\n return `property \"${path}\" is not an allowed enum value`;\n }\n return null;\n}\n\nfunction joinPath(path: string, key: string): string {\n return path.length === 0 ? key : `${path}.${key}`;\n}\n\nfunction matchesScalarType(type: 'string' | 'number' | 'integer' | 'boolean', value: unknown): boolean {\n switch (type) {\n case 'string':\n return typeof value === 'string';\n case 'boolean':\n return typeof value === 'boolean';\n case 'number':\n return typeof value === 'number' && Number.isFinite(value);\n case 'integer':\n return typeof value === 'number' && Number.isInteger(value);\n }\n}\n\ntype LogLevel = 'error' | 'warn' | 'info';\n\n/** The closed vocabulary a log consumer discriminates on. Nothing else is ever emitted. */\ntype ContentEvent =\n | 'content.read.ok'\n | 'content.read.stale'\n | 'content.read.missing'\n | 'content.read.invalid'\n | 'content.read.failed'\n | 'content.elements.omitted'\n | 'content.retention.skipped';\n\n/** One omitted element, as the aggregate line reports it. `reason` is a path, never a value. */\ntype ElementOmission = { elementId: string; modelId: string; reason: string };\n\n/**\n * Severity as a function of the read's status, and nothing else: `unavailable` means the runtime\n * could not read, which is the on-call signal; `not-found` and `invalid` mean the content is broken\n * while the runtime works. `source` is a payload field, so a preview read that cannot resolve is a\n * failure like any other. `invalid` is reachable from the preview branch only — the published path\n * throws on a bad body, which is why the store covers that case.\n */\nconst READ_EVENTS: Record<ContentEntryResult['status'], { level: LogLevel; event: ContentEvent }> = {\n found: { level: 'info', event: 'content.read.ok' },\n 'not-found': { level: 'warn', event: 'content.read.missing' },\n invalid: { level: 'warn', event: 'content.read.invalid' },\n unavailable: { level: 'error', event: 'content.read.failed' },\n};\n\n/**\n * The one line the status does not decide: the read failed and the stored blob covered it. `warn`\n * because the page rendered, which keeps `error` meaning exactly \"the shopper is seeing holes\". A\n * covered outage never escalates on its own, so alerting on one means filtering this event name.\n */\nconst STALE_READ_EVENT: { level: LogLevel; event: ContentEvent } = { level: 'warn', event: 'content.read.stale' };\n\n/**\n * The runtime's one log line: a constant message plus a payload whose `event` is the sole event\n * discriminator, so rewording prose cannot break a consumer. Nothing here ever carries a content\n * value, a preview token, or the broker secret.\n *\n * `entryId` is high-cardinality and stays log-only. Feeding these events to a metrics pipeline —\n * which no part of this runtime has today — means dropping or hashing it at that boundary; the six\n * event names are the bounded dimension set such a pipeline would inherit.\n */\nfunction logEvent(level: LogLevel, event: ContentEvent, fields: Record<string, unknown>): void {\n // `event` spreads last so a `fields` key of the same name cannot displace the discriminator every\n // consumer keys on. `fields` is a `Record<string, unknown>`, so nothing in the types would object.\n console[level]('[content-runtime]', { ...fields, event });\n}\n\nfunction transientReason(error: unknown): string {\n if (isTimeoutError(error)) return 'timeout';\n return error instanceof Error ? error.message : 'network error';\n}\n\n/**\n * The exact UTF-8 byte count of the serialized blob, not the JS string's UTF-16 code-unit count\n * (`.length`). Non-ASCII text — accented Latin, common in Portuguese-language content, at 2 bytes per\n * code unit, or CJK at 3 — makes those two numbers diverge, and a check against `.length` would let a\n * blob past both ceilings while genuinely over them in bytes.\n */\nfunction blobByteLength(blob: Record<string, unknown>): number {\n return Buffer.byteLength(JSON.stringify(blob), 'utf8');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Own-property read. Every object keyed by an authored id here comes from `JSON.parse` or a plain\n * literal, so it carries `Object.prototype`: a key named `__proto__` or `constructor` would resolve\n * to something inherited instead of to `undefined`, and the absent-key branch would never be taken.\n */\nfunction ownValue<T>(source: Record<string, T>, key: string): T | undefined {\n return Object.hasOwn(source, key) ? source[key] : undefined;\n}\n\n/** Property check instead of `instanceof`: `AbortSignal.timeout` rejects with a `DOMException`. */\nfunction isTimeoutError(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false;\n const { name } = error as { name?: unknown };\n return name === 'TimeoutError' || name === 'AbortError';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"create-content-runtime.js","names":[],"sources":["../../../src/sdk/content/create-content-runtime.tsx"],"sourcesContent":["import { createHmac, timingSafeEqual } from 'node:crypto';\nimport type { ContentPropertySchema, ContentTypeDefinition } from '@vtex/fs-core';\nimport { cookies, draftMode } from 'next/headers';\nimport { connection } from 'next/server';\nimport { cache, type FC, type ReactNode } from 'react';\nimport { ownValue } from '../../utils/own-value.js';\n\nexport type ContentRuntimeConfig = {\n account: string;\n storeId: string;\n /** Canonical per-component models, verbatim from the aggregated content catalog, keyed by id. */\n contentTypes: Record<string, ContentTypeDefinition>;\n};\n\n/** The coordinates of one composite read. `locale` arrives per call, so one build serves many. */\nexport type ReadContentSlicesArgs = {\n contentTypeId: string;\n entryId: string;\n locale: string;\n /** `element.id → per-component model id` for every content-backed element in the scope. */\n elements: Record<string, string>;\n};\n\n/**\n * The scope's slices, keyed by `element.id`. A key is absent when its element must be omitted — a\n * missing key, a shape violation, or a whole-entry failure.\n *\n * The value type is unchecked on purpose: the generated page forwards it to a component whose\n * `$content` is `ContentValue<that component's model>`, a type that exists only in the component\n * package, and an index signature cannot satisfy a model's required properties. Stated once here\n * rather than cast at every emitted prop; the read below validates every value against its model.\n */\nexport type ContentSlices = {\n // biome-ignore lint/suspicious/noExplicitAny: the model's type lives in the component package, not here.\n [elementId: string]: any;\n};\n\nexport type ContentEntryBoundaryProps = {\n /** This element's slice, read at the top of the scope. `undefined` omits the component. */\n value: unknown;\n children: ReactNode;\n};\n\n/** The coordinates of one item read. Object arguments, so two strings cannot be swapped at the call site. */\nexport type ReadContentItemArgs = {\n contentTypeId: string;\n slug: string;\n locale: string;\n};\n\n/**\n * One item read. `found` is the resolved entry. `not-found` is HTTP 404 only. `unavailable` is every\n * other failure with nothing stored. There is no `invalid` arm: a 200 JSON object is the blob.\n */\nexport type ContentItemResult =\n | { status: 'found'; blob: Record<string, unknown> }\n | { status: 'not-found' }\n | { status: 'unavailable'; reason: string };\n\nexport type ContentRuntime = {\n readContentSlices: (args: ReadContentSlicesArgs) => Promise<ContentSlices>;\n readContentItem: (args: ReadContentItemArgs) => Promise<ContentItemResult>;\n ContentEntryBoundary: FC<ContentEntryBoundaryProps>;\n};\n\n/** The signed preview context the Draft Mode route stores; re-verified here before use. */\ntype PreviewClaims = {\n account: string;\n storeId: string;\n branch: string;\n /** Millisecond epoch the token was minted / expires; a captured cookie stops working past `exp`. */\n iat: number;\n exp: number;\n};\n\n/**\n * Internal result taxonomy: a read either finds a validated composite blob or fails for a definite,\n * loggable reason. A transient upstream failure resolves `found` from the last-good store below when\n * that store holds the entry, and degrades to `unavailable` — omitting the affected components —\n * only when it does not.\n */\nexport type ContentEntryResult =\n | { status: 'found'; blob: Record<string, unknown> }\n | { status: 'not-found' }\n | { status: 'unavailable'; reason: string }\n | { status: 'invalid'; reason: string };\n\n/**\n * One published read's outcome. `staleReason` is present only when the fresh read failed and the\n * store covered it, and the result is then an ordinary `found`: no consumer has a fifth renderable\n * status to learn, and only the log line tells a served blob from a fresh one.\n */\ntype PublishedRead = { result: ContentEntryResult; staleReason?: string };\n\n/** Same shape as `PublishedRead`, on the item taxonomy (no `invalid` arm). */\ntype PublishedItemRead = { result: ContentItemResult; staleReason?: string };\n\nconst ENTRY_REQUEST_TIMEOUT_MS = 5000;\n\n/**\n * Retention ceilings for the last-good store, both estimated rather than measured: no production\n * store exists yet to size a real composite against, and the example schemas in this repo are demo\n * content, not authored pages, so neither is evidence of real scale.\n *\n * `MAX_RETAINED_BLOB_BYTES` bounds one entry. Ordinary editorial text (a title, a body, an image URL,\n * an alt text) stays in the hundreds of bytes to a few kilobytes per element regardless of how many\n * elements a page composes — even the largest composed layout in this repo (103 elements) does not\n * read as megabytes if every element stays plain text. The realistic way to exceed this is a single\n * field holding embedded binary — a base64 image pasted into a text field instead of uploaded through\n * the media widget — which is exactly the shape this guards against, independent of element count.\n *\n * `MAX_RETAINED_TOTAL_BYTES` bounds the whole store, and is the one that actually matters: the\n * per-blob cap alone does not bound total memory, because it multiplies by key count, and key count is\n * not small — a large store's pages and layouts, times its locales, is plausibly a few hundred keys.\n * A few hundred keys at a few megabytes each already exceeds a typical serverless heap (512 MB–1 GB)\n * on the per-blob cap alone; ~64 MB is a small slice of that heap while still holding many ordinary\n * pages at once.\n *\n * Both numbers count serialized JSON text (`blobByteLength`), not retained heap: a parsed object\n * typically costs more than its JSON text — V8 pays per-property and per-string object overhead that\n * the text itself does not — so the real footprint at either ceiling is larger than the number on the\n * tin, plausibly by a small multiple. Both ceilings still leave headroom against a 512 MB–1 GB heap at\n * that multiple; sizing them as retained heap instead would need a runtime memory measurement this\n * module has no cheap way to take.\n *\n * Both numbers are round and generous on purpose, not derived. `content.retention.skipped` below is\n * the real calibration path: once a production store exists, its log volume says whether either\n * ceiling needs to move.\n */\nconst MAX_RETAINED_BLOB_BYTES = 1_000_000;\nconst MAX_RETAINED_TOTAL_BYTES = 64_000_000;\n\n/** Server-only preview credential, supplied at deployment time; never reaches the browser. */\nconst PREVIEW_TOKEN_ENV = 'CONTENT_PLATFORM_PREVIEW_TOKEN';\n/** Shared broker secret used to verify the signed preview context cookie. */\nconst PREVIEW_SECRET_ENV = 'CONTENT_PREVIEW_SECRET';\n/** The signed preview context cookie the Draft Mode route sets. */\nconst PREVIEW_COOKIE = '__fs_content_preview';\n\n/**\n * Creates the one server-side content read surface for a store: a `React.cache`-deduped\n * `readContentSlices` the generated page or layout awaits once at the top, and the\n * `ContentEntryBoundary` the compiler wraps around each content-backed element. One read per scope\n * yields every element's `$content` value keyed by `element.id`, so a page issues at most two Data\n * Plane reads (its own entry plus its layout's) regardless of content-backed component count. Draft\n * Mode switches that composite read to Control Plane's preview endpoint through a server-only\n * credential. `readContentItem` reads one entry by slug and does not consult Draft Mode.\n */\nexport function createContentRuntime(config: ContentRuntimeConfig): ContentRuntime {\n // The blob of the most recent successful published read, per entry, so a failing Data Plane\n // renders the last content that worked instead of holes. Not `React.cache`, whose lifetime is the\n // request and so shorter than the failure it has to cover, and not module scope, which every\n // runtime in the process would share — two of them on the same coordinates would then serve each\n // other's content. The generated consumer holds the runtime in a module-scope `const`, so the\n // closure lives as long as the process either way.\n //\n // `contentTypeId` and `entryId` are compiler-emitted literals for the life of the deployment (a\n // dynamic route gets one entry, not one per slug), so today's key domain is small: pages plus\n // layouts, times locales. That is an invariant of the generated caller, not of this function —\n // nothing here stops a future one from passing a request-derived value instead, and the moment it\n // does, this map grows with traffic rather than with the store's page count.\n //\n // Item reads share this map and both ceilings. Their key starts with `item` and a NUL, then the\n // content type, slug, and locale, so it cannot equal a composite key even when the slug equals an\n // entry id. A slug is request-derived, so that part of the map grows with distinct items.\n const lastGood = new Map<string, Record<string, unknown>>();\n let retainedBytes = 0;\n\n type RetainFields =\n | { contentTypeId: string; entryId: string; locale: string }\n | { contentTypeId: string; slug: string; locale: string };\n\n /**\n * Whether a successful read's blob replaces what `key` already holds, weighed against both\n * ceilings. Skipping never deletes: whatever `key` held before, if anything, is left exactly as it\n * was — only the update is declined, so a rare oversized read cannot erase a smaller last-good value\n * that a future failure could still use.\n */\n function retainBlob(key: string, blob: Record<string, unknown>, fields: RetainFields): void {\n const size = blobByteLength(blob);\n if (size > MAX_RETAINED_BLOB_BYTES) {\n logEvent('warn', 'content.retention.skipped', { ...fields, bytes: size, reason: 'blob-too-large' });\n return;\n }\n const existing = lastGood.get(key);\n const existingSize = existing ? blobByteLength(existing) : 0;\n if (retainedBytes - existingSize + size > MAX_RETAINED_TOTAL_BYTES) {\n logEvent('warn', 'content.retention.skipped', { ...fields, bytes: size, reason: 'total-cap-reached' });\n return;\n }\n lastGood.set(key, blob);\n retainedBytes += size - existingSize;\n }\n\n /**\n * The published read plus the fallback that covers it. The upstream is always called and the store\n * is read only in the `catch`, which is what makes this a fallback and not a cache: a publish is\n * visible on the very next request. A success ordinarily overwrites the entry (see `retainBlob` for\n * the two cases where it does not), a `not-found` leaves it alone so unpublishing still unpublishes,\n * and nothing is ever removed or expired by age.\n *\n * Failure is structural — whatever leaves `readPublishedEntry` as a throw — rather than a status\n * table, so a 403 or a 429 serves the stored blob, and a failure mode added there later is covered\n * without touching this.\n */\n async function readPublished(contentTypeId: string, entryId: string, locale: string): Promise<PublishedRead> {\n // NUL appears in no content-type id, entry id, or BCP-47 tag, so two distinct triples cannot\n // collide. Keyed on the same three values the memoisation below uses, so the two layers never\n // disagree about what \"the same read\" is.\n const key = `${contentTypeId}\\u0000${entryId}\\u0000${locale}`;\n try {\n const result = await readPublishedEntry(config.account, config.storeId, locale, contentTypeId, entryId);\n if (result.status === 'found') {\n retainBlob(key, result.blob, { contentTypeId, entryId, locale });\n }\n return { result };\n } catch (error) {\n const reason = transientReason(error);\n const stored = lastGood.get(key);\n // Nothing stored: omit the affected components while the rest of the route renders.\n if (stored === undefined) return { result: { status: 'unavailable', reason } };\n return { result: { status: 'found', blob: stored }, staleReason: reason };\n }\n }\n\n /**\n * The by-slug read plus the same fallback. Not a parameter of `readPublished`: the key, the URL,\n * and the result taxonomy differ, and folding them together would put a slug on the composite log\n * line. A `not-found` returns before the map is read or written.\n */\n async function readPublishedItem(contentTypeId: string, slug: string, locale: string): Promise<PublishedItemRead> {\n // The `item` prefix is what keeps this off `${contentTypeId}\\u0000${entryId}\\u0000${locale}`.\n const key = `item\\u0000${contentTypeId}\\u0000${slug}\\u0000${locale}`;\n try {\n const result = await readItemBySlug(config.account, config.storeId, locale, contentTypeId, slug);\n if (result.status === 'found') {\n retainBlob(key, result.blob, { contentTypeId, slug, locale });\n }\n return { result };\n } catch (error) {\n const reason = transientReason(error);\n const stored = lastGood.get(key);\n if (stored === undefined) return { result: { status: 'unavailable', reason } };\n return { result: { status: 'found', blob: stored }, staleReason: reason };\n }\n }\n\n // React.cache collapses same-entry awaits within one request: N boundaries reading the same\n // composite entry share one upstream read and one read-log line. That is per-request memoisation\n // only — nothing is reused across requests — so it is `readPublished` above, not this, that\n // carries a value across the failure it covers. Either way a read never throws a route error.\n const read = cache(async (contentTypeId: string, entryId: string, locale: string): Promise<ContentEntryResult> => {\n // Content is read per request, never at build time. Nothing else on a content-only page is\n // request-dependent — `draftMode()` prerenders as disabled and an uncached `fetch` does not bail\n // out on its own — so without this the entry is captured by a static prerender and the store\n // stops tracking the CMS until its next deploy. `'use cache'` used to hold the route open with a\n // five-minute revalidate; removing it for a platform that does not stream took that with it.\n // Outside the fallback's try, which lives in `readPublished`, so the bail-out it throws during\n // static generation is never swallowed as a transient failure.\n await connection();\n const { isEnabled: draft } = await draftMode();\n let result: ContentEntryResult;\n let staleReason: string | undefined;\n if (draft) {\n // The preview branch neither reads the store nor writes it: an unpublished blob must not\n // outlive the request that asked for it, and a draft must never cover a published failure.\n result = await readPreviewEntry(config, contentTypeId, entryId, locale);\n } else {\n ({ result, staleReason } = await readPublished(contentTypeId, entryId, locale));\n }\n // A served blob is `found`, so the status alone would report it as a success.\n const stale = staleReason !== undefined;\n const { level, event } = stale ? STALE_READ_EVENT : READ_EVENTS[result.status];\n const reason =\n staleReason ?? (result.status === 'unavailable' || result.status === 'invalid' ? result.reason : undefined);\n // The success line is the cheapest evidence of the ≤2-reads-per-page guarantee, and its only\n // cost is production volume: nothing caches this read, so it would emit once or twice per page\n // view and bury the rare failure line. Suppressed there, kept everywhere the count is checked —\n // and the gate covers a genuine success only, so a covered outage stays audible in production.\n const suppressed = !stale && result.status === 'found' && process.env.NODE_ENV === 'production';\n if (!suppressed) {\n logEvent(level, event, {\n contentTypeId,\n entryId,\n locale,\n source: draft ? 'control-plane' : 'data-plane',\n ...(reason === undefined ? {} : { reason }),\n });\n }\n return result;\n });\n\n /**\n * The scope's one read, awaited before render: the composite entry, sliced into one `$content`\n * value per content-backed element. The values reach the components as literal props, so they are\n * part of the server render by construction — a `cloneElement` from a client-side provider lands\n * only on hydration, which is what made server HTML and client DOM disagree.\n *\n * The public shape takes one object, so a call site cannot silently swap two same-typed positional\n * arguments; `React.cache` keys on the positional ones underneath.\n */\n async function readContentSlices({\n contentTypeId,\n entryId,\n locale,\n elements,\n }: ReadContentSlicesArgs): Promise<ContentSlices> {\n const result = await read(contentTypeId, entryId, locale);\n if (result.status !== 'found') {\n // A whole-entry failure (404, invalid, or an upstream failure with nothing stored for the\n // entry) omits every component sharing it: no key is present, so every boundary renders\n // nothing. A failure the last-good store covered never arrives here — it arrives as `found`.\n return emptySlices();\n }\n const slices = emptySlices();\n const requested = Object.entries(elements);\n const omissions: ElementOmission[] = [];\n for (const [elementId, modelId] of requested) {\n const slice = sliceContent(result.blob, elementId, ownValue(config.contentTypes, modelId));\n if (slice.status === 'omitted') {\n // A missing or invalid key within an otherwise-valid entry omits only this one component.\n omissions.push({ elementId, modelId, reason: slice.reason });\n continue;\n }\n slices[elementId] = slice.content;\n }\n if (omissions.length > 0) {\n // One line per call rather than one per element: the operational fact is the ratio, and\n // `omitted === total` is what tells a blank scope from a partial one. The level says only that\n // the page rendered, so it stays `warn` at every ratio.\n logEvent('warn', 'content.elements.omitted', {\n contentTypeId,\n entryId,\n locale,\n omitted: omissions.length,\n total: requested.length,\n omissions,\n });\n }\n return slices;\n }\n\n // A separate cache from `read`. Positional key is content type, slug, locale. `connection()` is\n // outside `readPublishedItem`'s try, so a prerender bail-out propagates unlogged. Draft Mode is\n // not a branch this read has: there is no slug preview route, and a Data Plane `branch` query is\n // ignored by the platform.\n const readItem = cache(async (contentTypeId: string, slug: string, locale: string): Promise<ContentItemResult> => {\n await connection();\n const { result, staleReason } = await readPublishedItem(contentTypeId, slug, locale);\n const stale = staleReason !== undefined;\n const { level, event } = stale ? STALE_READ_EVENT : READ_EVENTS[result.status];\n const reason = staleReason ?? (result.status === 'unavailable' ? result.reason : undefined);\n const suppressed = !stale && result.status === 'found' && process.env.NODE_ENV === 'production';\n if (!suppressed) {\n logEvent(level, event, {\n contentTypeId,\n slug,\n locale,\n source: 'data-plane',\n ...(reason === undefined ? {} : { reason }),\n });\n }\n return result;\n });\n\n async function readContentItem({ contentTypeId, slug, locale }: ReadContentItemArgs): Promise<ContentItemResult> {\n return readItem(contentTypeId, slug, locale);\n }\n\n return { readContentSlices, readContentItem, ContentEntryBoundary };\n}\n\n/**\n * The slice map, with no prototype. An element id is a schema string, so `__proto__` is a name the\n * map can be asked for; on a plain object the lookup would inherit `Object.prototype` and a boundary\n * would render an element whose `$content` is that object rather than omitting it. The failure path\n * returns this too — an entry that 404s must answer `undefined` for every key, including that one.\n */\nfunction emptySlices(): ContentSlices {\n return Object.create(null) as ContentSlices;\n}\n\n/**\n * Renders its element only when that element's slice is present — the omission rule the content RFC\n * states. It no longer delivers the value (the page passes `$content` as a literal prop), so it\n * clones nothing and what wraps it no longer decides whether content arrives.\n *\n * Module scope, not a closure over `createContentRuntime`: it reads nothing from the runtime's\n * configuration, so every store shares one component identity.\n */\nfunction ContentEntryBoundary({ value, children }: ContentEntryBoundaryProps): ReactNode {\n return value === undefined ? null : children;\n}\n\n/**\n * The published read: one composite entry from the Data Plane, live on every request. A definitive\n * 404 resolves a `not-found` sentinel; a transient failure (network, timeout, 5xx, malformed body)\n * throws, and `readPublished` decides between the stored blob and an omit.\n *\n * Nothing caches it. This read sat under `'use cache'` until Cache Components was removed for a\n * platform that does not stream, and the five-minute `cacheLife` and 24-hour `expire` went with it —\n * a transient failure used to throw inside the cache and Next kept serving the last-good entry for\n * up to a day. What replaced that is the last-good store, which is a fallback and not a cache: it\n * never shortens or skips this read, so the happy path is unchanged and a publish lands on the very\n * next request. The page cache, the two cache tags this read emitted and the tag-based revalidation\n * route they fed are still gone, and still owed by whatever restores caching.\n */\nasync function readPublishedEntry(\n account: string,\n storeId: string,\n locale: string,\n contentTypeId: string,\n entryId: string,\n): Promise<ContentEntryResult> {\n const url = buildDataPlaneUrl(account, storeId, locale, contentTypeId, entryId);\n const response = await fetch(url, { signal: AbortSignal.timeout(ENTRY_REQUEST_TIMEOUT_MS) });\n\n if (response.status === 404) return { status: 'not-found' };\n if (!response.ok) throw new Error(`content upstream status ${response.status}`);\n\n let payload: unknown;\n try {\n payload = await response.json();\n } catch {\n throw new Error('content response is not JSON');\n }\n if (!isRecord(payload)) throw new Error('content response is not an object');\n\n return { status: 'found', blob: payload };\n}\n\n/**\n * One entry from the Data Plane by slug. A 404 resolves `not-found` before `json()`. Any other\n * non-OK status, a non-JSON body, or a non-object body throws, and `readPublishedItem` decides\n * between the stored blob and `unavailable`. A 200 object is `found` with no field-schema check.\n * `locale_metadata` is the platform envelope (it carries an inline SVG) and this path has no slicer,\n * so that own property is removed before the blob is returned or retained. No other key is dropped.\n * A list body is not an object, so it throws rather than resolving `not-found`.\n */\nasync function readItemBySlug(\n account: string,\n storeId: string,\n locale: string,\n contentTypeId: string,\n slug: string,\n): Promise<ContentItemResult> {\n const url = buildDataPlaneSlugUrl(account, storeId, locale, contentTypeId, slug);\n const response = await fetch(url, { signal: AbortSignal.timeout(ENTRY_REQUEST_TIMEOUT_MS) });\n\n if (response.status === 404) return { status: 'not-found' };\n if (!response.ok) throw new Error(`content upstream status ${response.status}`);\n\n let payload: unknown;\n try {\n payload = await response.json();\n } catch {\n throw new Error('content response is not JSON');\n }\n if (!isRecord(payload)) throw new Error('content response is not an object');\n if (Object.hasOwn(payload, 'locale_metadata')) {\n delete payload.locale_metadata;\n }\n\n return { status: 'found', blob: payload };\n}\n\n/**\n * The Draft Mode read: the same composite entry resolved through Control Plane's preview endpoint\n * with a server-only credential, bypassing all caches. The branch comes from a SIGNED preview\n * context cookie whose HMAC is re-verified here against the broker secret — a tampered cookie fails\n * closed, so a valid session cannot read an unauthorized branch's drafts. The credential never\n * enters browser code, logs, URLs, or props. Uncached, so its sentinels are never persisted.\n */\nasync function readPreviewEntry(\n config: ContentRuntimeConfig,\n contentTypeId: string,\n entryId: string,\n locale: string,\n): Promise<ContentEntryResult> {\n const token = process.env[PREVIEW_TOKEN_ENV];\n if (!token) return { status: 'unavailable', reason: 'preview credential unavailable' };\n const secret = process.env[PREVIEW_SECRET_ENV];\n if (!secret) return { status: 'unavailable', reason: 'preview secret unavailable' };\n\n const store = await cookies();\n const raw = store.get(PREVIEW_COOKIE)?.value;\n const claims = raw ? verifyPreviewContext(raw, secret) : null;\n if (!claims || claims.account !== config.account || claims.storeId !== config.storeId || !claims.branch) {\n return { status: 'unavailable', reason: 'invalid preview context' };\n }\n\n const { account, storeId } = config;\n const path = [account, storeId, contentTypeId, 'entries', 'preview', entryId].map(encodeURIComponent).join('/');\n const url = `https://${account}.vtexcommercestable.com.br/api/content-platform/manage/${path}?branchId=${encodeURIComponent(claims.branch)}&locale=${encodeURIComponent(locale)}`;\n return fetchPreview(url, token);\n}\n\nasync function fetchPreview(url: string, token: string): Promise<ContentEntryResult> {\n let response: Response;\n try {\n response = await fetch(url, {\n cache: 'no-store',\n headers: { Authorization: `Bearer ${token}` },\n signal: AbortSignal.timeout(ENTRY_REQUEST_TIMEOUT_MS),\n });\n } catch (error) {\n return { status: 'unavailable', reason: isTimeoutError(error) ? 'timeout' : 'network error' };\n }\n if (response.status === 404) return { status: 'not-found' };\n if (!response.ok) return { status: 'unavailable', reason: `upstream status ${response.status}` };\n let payload: unknown;\n try {\n payload = await response.json();\n } catch {\n return { status: 'invalid', reason: 'response body is not JSON' };\n }\n if (!isRecord(payload)) return { status: 'invalid', reason: 'response body is not an object' };\n return { status: 'found', blob: payload };\n}\n\n/**\n * Verifies a signed preview context cookie (`base64url(claims).base64url(hmac)`) against the broker\n * secret with a constant-time comparison, returning the claims only when the signature holds and the\n * token has not expired — a captured cookie fails closed once past `exp`.\n */\nfunction verifyPreviewContext(raw: string, secret: string): PreviewClaims | null {\n const [body, signature] = raw.split('.');\n if (!body || !signature) return null;\n const expected = createHmac('sha256', secret).update(body).digest('base64url');\n const given = Buffer.from(signature, 'utf8');\n const want = Buffer.from(expected, 'utf8');\n if (given.length !== want.length || !timingSafeEqual(given, want)) return null;\n try {\n const claims = JSON.parse(Buffer.from(body, 'base64url').toString('utf8')) as PreviewClaims;\n if (typeof claims.branch !== 'string' || !Number.isFinite(claims.exp) || Date.now() >= claims.exp) {\n return null;\n }\n return claims;\n } catch {\n return null;\n }\n}\n\nfunction buildDataPlaneUrl(\n account: string,\n storeId: string,\n locale: string,\n contentTypeId: string,\n entryId: string,\n): string {\n const path = [account, storeId, contentTypeId, 'entries', entryId].map(encodeURIComponent).join('/');\n return `https://${account}.vtexcommercestable.com.br/api/content-platform/data/${path}?locale=${encodeURIComponent(locale)}`;\n}\n\n/** Same origin and `locale` query as `buildDataPlaneUrl`. The slug is one segment: it is not split on `/`. */\nfunction buildDataPlaneSlugUrl(\n account: string,\n storeId: string,\n locale: string,\n contentTypeId: string,\n slug: string,\n): string {\n const path = [account, storeId, contentTypeId, 'entries', 'slug', slug].map(encodeURIComponent).join('/');\n return `https://${account}.vtexcommercestable.com.br/api/content-platform/data/${path}?locale=${encodeURIComponent(locale)}`;\n}\n\ntype ContentSliceResult =\n | { status: 'sliced'; content: Record<string, unknown> }\n | { status: 'omitted'; reason: string };\n\n/**\n * Slices one element's value out of the composite blob by its `element.id` and validates it against\n * that element's per-component model. Omits only this component on a missing key, a non-object\n * value, an unknown model, or a shape violation — keyword constraints are the preflight's job.\n * Failing closed on an unknown model keeps an unvalidated blob from reaching `$content` (which would\n * crash the route as a React child).\n */\nfunction sliceContent(\n blob: Record<string, unknown>,\n elementId: string,\n model: ContentTypeDefinition | undefined,\n): ContentSliceResult {\n const raw = ownValue(blob, elementId);\n if (!isRecord(raw)) return { status: 'omitted', reason: 'missing or non-object value' };\n if (!model) return { status: 'omitted', reason: 'unknown model' };\n\n const content = projectObject(model.schema.properties, raw);\n const violation = findShapeViolation(model, content);\n if (violation !== null) return { status: 'omitted', reason: violation };\n return { status: 'sliced', content };\n}\n\n/** A closed object shape, at the root or one level down. */\ntype ClosedObjectShape = {\n required?: readonly string[];\n properties: Record<string, ContentPropertySchema>;\n};\n\n/**\n * Keeps declared keys only, at every level the vocabulary admits, so the injected value matches the\n * `ContentValue` type the component was compiled against. A value of the wrong shape passes through\n * untouched for the validator below to reject.\n */\nfunction projectObject(\n properties: Record<string, ContentPropertySchema>,\n raw: Record<string, unknown>,\n): Record<string, unknown> {\n const projected: Record<string, unknown> = {};\n for (const [key, property] of Object.entries(properties)) {\n const value = ownValue(raw, key);\n if (value === undefined) continue;\n if (property.type === 'array') {\n projected[key] = Array.isArray(value)\n ? value.map((member) => (isRecord(member) ? projectObject(property.items.properties, member) : member))\n : value;\n } else if (property.type === 'object') {\n projected[key] = isRecord(value) ? projectObject(property.properties, value) : value;\n } else {\n projected[key] = value;\n }\n }\n return projected;\n}\n\n/** Shape-level validation only (required presence, primitive type, enum membership), at every level. */\nfunction findShapeViolation(model: ContentTypeDefinition, content: Record<string, unknown>): string | null {\n return findObjectViolation('', model.schema, content);\n}\n\nfunction findObjectViolation(path: string, shape: ClosedObjectShape, value: Record<string, unknown>): string | null {\n for (const key of shape.required ?? []) {\n if (ownValue(value, key) === undefined) return `missing required property \"${joinPath(path, key)}\"`;\n }\n for (const [key, property] of Object.entries(shape.properties)) {\n const member = ownValue(value, key);\n if (member === undefined) continue;\n const violation = findPropertyViolation(joinPath(path, key), property, member);\n if (violation !== null) return violation;\n }\n return null;\n}\n\nfunction findPropertyViolation(path: string, property: ContentPropertySchema, value: unknown): string | null {\n if (property.type === 'array') {\n if (!Array.isArray(value)) return `property \"${path}\" does not match type \"array\"`;\n for (const [index, member] of value.entries()) {\n const itemPath = `${path}[${index}]`;\n if (!isRecord(member)) return `property \"${itemPath}\" does not match type \"object\"`;\n const violation = findObjectViolation(itemPath, property.items, member);\n if (violation !== null) return violation;\n }\n // minItems and maxItems are author-time bounds; the read path does not enforce them.\n return null;\n }\n if (property.type === 'object') {\n if (!isRecord(value)) return `property \"${path}\" does not match type \"object\"`;\n return findObjectViolation(path, property, value);\n }\n if (!matchesScalarType(property.type, value)) return `property \"${path}\" does not match type \"${property.type}\"`;\n if (property.enum && !property.enum.includes(value as string | number)) {\n return `property \"${path}\" is not an allowed enum value`;\n }\n return null;\n}\n\nfunction joinPath(path: string, key: string): string {\n return path.length === 0 ? key : `${path}.${key}`;\n}\n\nfunction matchesScalarType(type: 'string' | 'number' | 'integer' | 'boolean', value: unknown): boolean {\n switch (type) {\n case 'string':\n return typeof value === 'string';\n case 'boolean':\n return typeof value === 'boolean';\n case 'number':\n return typeof value === 'number' && Number.isFinite(value);\n case 'integer':\n return typeof value === 'number' && Number.isInteger(value);\n }\n}\n\ntype LogLevel = 'error' | 'warn' | 'info';\n\n/** The closed vocabulary a log consumer discriminates on. Nothing else is ever emitted. */\ntype ContentEvent =\n | 'content.read.ok'\n | 'content.read.stale'\n | 'content.read.missing'\n | 'content.read.invalid'\n | 'content.read.failed'\n | 'content.elements.omitted'\n | 'content.retention.skipped';\n\n/** One omitted element, as the aggregate line reports it. `reason` is a path, never a value. */\ntype ElementOmission = { elementId: string; modelId: string; reason: string };\n\n/**\n * Severity as a function of the read's status, and nothing else: `unavailable` means the runtime\n * could not read, which is the on-call signal; `not-found` and `invalid` mean the content is broken\n * while the runtime works. `source` is a payload field, so a preview read that cannot resolve is a\n * failure like any other. `invalid` is reachable from the preview branch only — the published path\n * throws on a bad body, which is why the store covers that case.\n */\nconst READ_EVENTS: Record<ContentEntryResult['status'], { level: LogLevel; event: ContentEvent }> = {\n found: { level: 'info', event: 'content.read.ok' },\n 'not-found': { level: 'warn', event: 'content.read.missing' },\n invalid: { level: 'warn', event: 'content.read.invalid' },\n unavailable: { level: 'error', event: 'content.read.failed' },\n};\n\n/**\n * The one line the status does not decide: the read failed and the stored blob covered it. `warn`\n * because the page rendered, which keeps `error` meaning exactly \"the shopper is seeing holes\". A\n * covered outage never escalates on its own, so alerting on one means filtering this event name.\n */\nconst STALE_READ_EVENT: { level: LogLevel; event: ContentEvent } = { level: 'warn', event: 'content.read.stale' };\n\n/**\n * The runtime's one log line: a constant message plus a payload whose `event` is the sole event\n * discriminator, so rewording prose cannot break a consumer. Nothing here ever carries a content\n * value, a preview token, or the broker secret.\n *\n * `entryId` is high-cardinality and stays log-only. Feeding these events to a metrics pipeline —\n * which no part of this runtime has today — means dropping or hashing it at that boundary; the six\n * event names are the bounded dimension set such a pipeline would inherit.\n */\nfunction logEvent(level: LogLevel, event: ContentEvent, fields: Record<string, unknown>): void {\n // `event` spreads last so a `fields` key of the same name cannot displace the discriminator every\n // consumer keys on. `fields` is a `Record<string, unknown>`, so nothing in the types would object.\n console[level]('[content-runtime]', { ...fields, event });\n}\n\nfunction transientReason(error: unknown): string {\n if (isTimeoutError(error)) return 'timeout';\n return error instanceof Error ? error.message : 'network error';\n}\n\n/**\n * The exact UTF-8 byte count of the serialized blob, not the JS string's UTF-16 code-unit count\n * (`.length`). Non-ASCII text — accented Latin, common in Portuguese-language content, at 2 bytes per\n * code unit, or CJK at 3 — makes those two numbers diverge, and a check against `.length` would let a\n * blob past both ceilings while genuinely over them in bytes.\n */\nfunction blobByteLength(blob: Record<string, unknown>): number {\n return Buffer.byteLength(JSON.stringify(blob), 'utf8');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Property check instead of `instanceof`: `AbortSignal.timeout` rejects with a `DOMException`. */\nfunction isTimeoutError(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false;\n const { name } = error as { name?: unknown };\n return name === 'TimeoutError' || name === 'AbortError';\n}\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { CombinedGraphQLErrors } from "@apollo/client/errors";
2
+ //#region src/sdk/data/failed-fields.ts
3
+ /**
4
+ * Response keys of the root fields a section cannot render from a result: `null` in `data` with an
5
+ * error whose `path` starts at them. With no data at all, every root an error's `path` starts at.
6
+ * Imported by both the server fetch and the client root, so it must stay free of either side.
7
+ */
8
+ function getFailedFields(data, error) {
9
+ if (!CombinedGraphQLErrors.is(error)) return [];
10
+ if (data != null && typeof data !== "object") return [];
11
+ const record = data;
12
+ const failed = [];
13
+ for (const { path } of error.errors) {
14
+ const key = path?.[0];
15
+ if (typeof key !== "string" || failed.includes(key)) continue;
16
+ if (record == null || record[key] === null) failed.push(key);
17
+ }
18
+ return failed;
19
+ }
20
+ //#endregion
21
+ export { getFailedFields };
22
+
23
+ //# sourceMappingURL=failed-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failed-fields.js","names":[],"sources":["../../../src/sdk/data/failed-fields.ts"],"sourcesContent":["import type { ErrorLike } from '@apollo/client';\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\n\n/**\n * Response keys of the root fields a section cannot render from a result: `null` in `data` with an\n * error whose `path` starts at them. With no data at all, every root an error's `path` starts at.\n * Imported by both the server fetch and the client root, so it must stay free of either side.\n */\nexport function getFailedFields(data: unknown, error: ErrorLike | undefined): string[] {\n if (!CombinedGraphQLErrors.is(error)) return [];\n if (data != null && typeof data !== 'object') return [];\n const record = data as Record<string, unknown> | null | undefined;\n const failed: string[] = [];\n for (const { path } of error.errors) {\n const key = path?.[0];\n if (typeof key !== 'string' || failed.includes(key)) continue;\n if (record == null || record[key] === null) failed.push(key);\n }\n return failed;\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,gBAAgB,MAAe,OAAwC;CACrF,IAAI,CAAC,sBAAsB,GAAG,KAAK,GAAG,OAAO,CAAC;CAC9C,IAAI,QAAQ,QAAQ,OAAO,SAAS,UAAU,OAAO,CAAC;CACtD,MAAM,SAAS;CACf,MAAM,SAAmB,CAAC;CAC1B,KAAK,MAAM,EAAE,UAAU,MAAM,QAAQ;EACnC,MAAM,MAAM,OAAO;EACnB,IAAI,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,GAAG;EACrD,IAAI,UAAU,QAAQ,OAAO,SAAS,MAAM,OAAO,KAAK,GAAG;CAC7D;CACA,OAAO;AACT"}
@@ -1,8 +1,8 @@
1
1
  import { DataOverrideContext, useDataOverride } from "./data-override-context.js";
2
- import "./entity-resolution-context.js";
2
+ import { ListItemRefContext, useListItemRef } from "./list-item-ref-context.js";
3
3
  import { PageReactiveDataRoot, ReactiveRootContext } from "./page-reactive-data-root.js";
4
+ import "./entity-resolution-context.js";
4
5
  import { HotDataProvider } from "./hot-data-provider.js";
5
- import { ListItemRefContext, useListItemRef } from "./list-item-ref-context.js";
6
6
  import { MultipleReactiveDataProvider } from "./multiple-reactive-data-provider.js";
7
7
  import { ReactiveDataProvider } from "./reactive-data-provider.js";
8
8
  import { ReactiveListItemDataProvider } from "./reactive-list-item-data-provider.js";
@@ -16,6 +16,12 @@ export type PageReactiveDataRootProps<TData, TVars extends OperationVariables> =
16
16
  serverVariables?: TVars;
17
17
  /** Server-rendered PAGE_QUERY result. When present and shape-matched, seeded into the cache once. */
18
18
  initialData?: TData | null;
19
+ /**
20
+ * Root fields that failed on the server. Non-empty: `initialData` is not seeded and the first read
21
+ * for these `serverVariables` is `network-only`. Read on mount and when `serverVariables` change;
22
+ * a change to `failedFields` alone, with the same variables, sends nothing.
23
+ */
24
+ failedFields?: readonly string[];
19
25
  /** Reactive vars whose current values are merged into operation variables at render time. */
20
26
  clientVariables?: ClientVariablesMap<TVars>;
21
27
  children: ReactNode;
@@ -31,6 +37,8 @@ export type ReactiveRootContextValue<TData = unknown> = Readonly<{
31
37
  * Error from the underlying useQuery. With non-null root fields (e.g. `cart: Cart!`), a failed
32
38
  * fetch nulls the whole operation's `data` — descendants need this to tell "fetch failed" apart
33
39
  * from "entity is legitimately absent" (docs/storefront-core/reference-free-particle-model.md § Read side — data).
40
+ * A nullable root field that failed on the server reaches here too: the root skips the seed and
41
+ * asks again, and this carries the browser's error beside the partial data.
34
42
  */
35
43
  error: ErrorLike | undefined;
36
44
  /**
@@ -43,14 +51,28 @@ export type ReactiveRootContextValue<TData = unknown> = Readonly<{
43
51
  * Required, not optional, and deliberately so. This flag is the whole of what arms the gate in
44
52
  * `useReactiveEntity`, so a provider that omits it disarms the gate silently — measured: a value
45
53
  * that `satisfies ReactiveRootContextValue` without it reproduces the corrupting payload byte for
46
- * byte on an otherwise-fixed tree, and no test fails. `ReactiveRootContext` is public surface via
47
- * `sdk/data/index.ts`, so that provider need not live in this repo. Making it required moves that
48
- * failure to compile time.
54
+ * byte on an otherwise-fixed tree, and no test fails. `ReactiveRootContext` ships through the
55
+ * package's `./internal/client` entry, so that provider need not live in this repo. Making it
56
+ * required moves that failure to compile time.
49
57
  *
50
58
  * A hand-built context value stands in for a root's *output*, not for the reactive runtime, so it
51
59
  * declares `false` and its subtree keeps gating on nothing.
52
60
  */
53
61
  hasRoot: boolean;
62
+ /**
63
+ * Re-runs the page query against the network with the root's current variables, the ones its
64
+ * latest render passed to `useQuery`, so a caller that changes a reactive variable calls it from an
65
+ * effect, not in the same handler. Sends nothing unless the root's last committed render had every
66
+ * required variable, nor after the root unmounted. Never rejects: a failure of the read arrives on
67
+ * `rootQueryData`, `loading`, and `error`, as it does on a first load, a cancelled refetch is
68
+ * silent, and any other failure is logged by its name and swallowed.
69
+ *
70
+ * Optional, unlike `hasRoot`: a provider that omits it only loses the in-place retry, where a
71
+ * provider that omits `hasRoot` corrupts what `useReactiveEntity` builds. `PageReactiveDataRoot`
72
+ * always sets it, but a provider built outside this repo may not, and a caller then falls back to
73
+ * reloading the page.
74
+ */
75
+ refetch?: () => Promise<void>;
54
76
  }>;
55
77
  export declare const ReactiveRootContext: import("react").Context<Readonly<{
56
78
  /** Current root query data exposed to descendant `ReactiveDataProvider`s. */
@@ -63,6 +85,8 @@ export declare const ReactiveRootContext: import("react").Context<Readonly<{
63
85
  * Error from the underlying useQuery. With non-null root fields (e.g. `cart: Cart!`), a failed
64
86
  * fetch nulls the whole operation's `data` — descendants need this to tell "fetch failed" apart
65
87
  * from "entity is legitimately absent" (docs/storefront-core/reference-free-particle-model.md § Read side — data).
88
+ * A nullable root field that failed on the server reaches here too: the root skips the seed and
89
+ * asks again, and this carries the browser's error beside the partial data.
66
90
  */
67
91
  error: ErrorLike | undefined;
68
92
  /**
@@ -75,15 +99,29 @@ export declare const ReactiveRootContext: import("react").Context<Readonly<{
75
99
  * Required, not optional, and deliberately so. This flag is the whole of what arms the gate in
76
100
  * `useReactiveEntity`, so a provider that omits it disarms the gate silently — measured: a value
77
101
  * that `satisfies ReactiveRootContextValue` without it reproduces the corrupting payload byte for
78
- * byte on an otherwise-fixed tree, and no test fails. `ReactiveRootContext` is public surface via
79
- * `sdk/data/index.ts`, so that provider need not live in this repo. Making it required moves that
80
- * failure to compile time.
102
+ * byte on an otherwise-fixed tree, and no test fails. `ReactiveRootContext` ships through the
103
+ * package's `./internal/client` entry, so that provider need not live in this repo. Making it
104
+ * required moves that failure to compile time.
81
105
  *
82
106
  * A hand-built context value stands in for a root's *output*, not for the reactive runtime, so it
83
107
  * declares `false` and its subtree keeps gating on nothing.
84
108
  */
85
109
  hasRoot: boolean;
110
+ /**
111
+ * Re-runs the page query against the network with the root's current variables, the ones its
112
+ * latest render passed to `useQuery`, so a caller that changes a reactive variable calls it from an
113
+ * effect, not in the same handler. Sends nothing unless the root's last committed render had every
114
+ * required variable, nor after the root unmounted. Never rejects: a failure of the read arrives on
115
+ * `rootQueryData`, `loading`, and `error`, as it does on a first load, a cancelled refetch is
116
+ * silent, and any other failure is logged by its name and swallowed.
117
+ *
118
+ * Optional, unlike `hasRoot`: a provider that omits it only loses the in-place retry, where a
119
+ * provider that omits `hasRoot` corrupts what `useReactiveEntity` builds. `PageReactiveDataRoot`
120
+ * always sets it, but a provider built outside this repo may not, and a caller then falls back to
121
+ * reloading the page.
122
+ */
123
+ refetch?: () => Promise<void>;
86
124
  }>>;
87
- export declare function PageReactiveDataRoot<TData = unknown, TVars extends OperationVariables = OperationVariables>(props: PageReactiveDataRootProps<TData, TVars>): ReactNode;
125
+ export declare function PageReactiveDataRoot<TData = unknown, TVars extends OperationVariables = OperationVariables>({ query, serverVariables, initialData, failedFields, clientVariables, children }: PageReactiveDataRootProps<TData, TVars>): ReactNode;
88
126
  //#endregion
89
127
  //# sourceMappingURL=page-reactive-data-root.d.ts.map
@@ -1,8 +1,11 @@
1
1
  "use client";
2
+ import { loggableErrorName } from "../../utils/loggable-error-name.js";
3
+ import { getFailedFields } from "./failed-fields.js";
2
4
  import { useApolloClient, useQuery } from "@apollo/client/react";
3
- import { createContext, useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
5
+ import { createContext, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
6
  import { jsx } from "react/jsx-runtime";
5
7
  import { Kind } from "graphql";
8
+ import { canonicalStringify } from "@apollo/client/utilities";
6
9
  //#region src/sdk/data/page-reactive-data-root.tsx
7
10
  const REACTIVE_VAR_REGISTRY = {};
8
11
  function resolveReactiveVar(value) {
@@ -35,6 +38,16 @@ function getTopLevelFieldNames(query) {
35
38
  for (const sel of op.selectionSet.selections) if (sel.kind === Kind.FIELD) names.add((sel.alias ?? sel.name).value);
36
39
  return names;
37
40
  }
41
+ function stillShowsFailure(data, error) {
42
+ return data == null || getFailedFields(data, error).length > 0;
43
+ }
44
+ function withEntry(errors, key, error) {
45
+ if (errors.get(key) === error) return errors;
46
+ const next = new Map(errors);
47
+ if (error) next.set(key, error);
48
+ else next.delete(key);
49
+ return next;
50
+ }
38
51
  function shapeMatches(query, data) {
39
52
  if (data == null || typeof data !== "object") return false;
40
53
  const expected = getTopLevelFieldNames(query);
@@ -96,10 +109,25 @@ function useClientVariableValues(clientVariables) {
96
109
  useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
97
110
  return lastSnapshotRef.current?.value ?? {};
98
111
  }
99
- function PageReactiveDataRoot(props) {
100
- const { query, serverVariables, initialData, clientVariables, children } = props;
112
+ /**
113
+ * Apollo rejects a refetch that ends with no value, as when the root unmounts mid-flight, with a
114
+ * `DOMException` named `AbortError`, or a plain `Error` of that name where there is no `DOMException`.
115
+ * A property check, since a `DOMException` is not always an `Error` instance (jsdom). The read is
116
+ * guarded because a `.name` getter can throw, and `refetch` must not reject.
117
+ */
118
+ function isAbort(failure) {
119
+ if (typeof failure !== "object" || failure === null) return false;
120
+ try {
121
+ return failure.name === "AbortError";
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
126
+ function PageReactiveDataRoot({ query, serverVariables, initialData, failedFields, clientVariables, children }) {
101
127
  const client = useApolloClient();
102
- const seededRef = useRef(false);
128
+ const seededKeysRef = useRef(/* @__PURE__ */ new Set());
129
+ const hasFailedFields = failedFields != null && failedFields.length > 0;
130
+ const serverVariablesKey = canonicalStringify(serverVariables ?? null);
103
131
  const clientValues = useClientVariableValues(clientVariables);
104
132
  const mergedVariables = {
105
133
  ...serverVariables ?? {},
@@ -112,7 +140,8 @@ function PageReactiveDataRoot(props) {
112
140
  return v !== null && v !== void 0;
113
141
  });
114
142
  useEffect(() => {
115
- if (seededRef.current) return;
143
+ if (seededKeysRef.current.has(serverVariablesKey)) return;
144
+ if (hasFailedFields) return;
116
145
  if (!serverVariables || initialData == null) return;
117
146
  if (!shapeMatches(query, initialData)) return;
118
147
  client.cache.writeQuery({
@@ -120,29 +149,76 @@ function PageReactiveDataRoot(props) {
120
149
  variables: serverVariables,
121
150
  data: initialData
122
151
  });
123
- seededRef.current = true;
152
+ seededKeysRef.current.add(serverVariablesKey);
124
153
  }, [
125
154
  client,
126
155
  query,
127
156
  serverVariables,
128
- initialData
157
+ serverVariablesKey,
158
+ initialData,
159
+ hasFailedFields
129
160
  ]);
130
- const { data, loading, error } = useQuery(query, {
161
+ const networkReadKeysRef = useRef(/* @__PURE__ */ new Set());
162
+ const nextFetchPolicy = useCallback((_current, { reason }) => {
163
+ if (!hasFailedFields) return "cache-first";
164
+ if (reason === "variables-changed" && !networkReadKeysRef.current.has(serverVariablesKey)) return "network-only";
165
+ networkReadKeysRef.current.add(serverVariablesKey);
166
+ return "cache-first";
167
+ }, [hasFailedFields, serverVariablesKey]);
168
+ const { data, loading, error, refetch: refetchQuery } = useQuery(query, {
131
169
  variables: mergedVariables,
132
- fetchPolicy: "cache-first",
170
+ fetchPolicy: hasFailedFields ? "network-only" : "cache-first",
171
+ nextFetchPolicy,
172
+ errorPolicy: "all",
133
173
  skip: !hasAllRequiredVariables
134
174
  });
175
+ const variablesKey = canonicalStringify(mergedVariables);
176
+ const [networkErrors, setNetworkErrors] = useState(() => /* @__PURE__ */ new Map());
177
+ const storedError = networkErrors.get(variablesKey);
178
+ const storedErrorApplies = storedError !== void 0 && !loading && stillShowsFailure(data, storedError);
179
+ useEffect(() => {
180
+ if (error) setNetworkErrors((errors) => withEntry(errors, variablesKey, error));
181
+ else if (loading || storedError !== void 0 && !storedErrorApplies) setNetworkErrors((errors) => withEntry(errors, variablesKey, void 0));
182
+ }, [
183
+ error,
184
+ loading,
185
+ variablesKey,
186
+ storedError,
187
+ storedErrorApplies
188
+ ]);
189
+ const rootError = error ?? (storedErrorApplies ? storedError : void 0);
190
+ const latestRef = useRef(null);
191
+ useLayoutEffect(() => {
192
+ latestRef.current = {
193
+ refetchQuery,
194
+ hasAllRequiredVariables
195
+ };
196
+ });
197
+ useLayoutEffect(() => () => {
198
+ latestRef.current = null;
199
+ }, []);
200
+ const refetch = useCallback(async () => {
201
+ const latest = latestRef.current;
202
+ if (!latest?.hasAllRequiredVariables) return;
203
+ try {
204
+ await latest.refetchQuery();
205
+ } catch (error_) {
206
+ if (!isAbort(error_)) console.error("[PageReactiveDataRoot] refetch failed", loggableErrorName(error_));
207
+ }
208
+ }, []);
135
209
  const contextValue = useMemo(() => ({
136
210
  rootQueryData: data ?? void 0,
137
211
  loading,
138
212
  hasAllRequiredVariables,
139
- error,
140
- hasRoot: true
213
+ error: rootError,
214
+ hasRoot: true,
215
+ refetch
141
216
  }), [
142
217
  data,
143
218
  loading,
144
219
  hasAllRequiredVariables,
145
- error
220
+ rootError,
221
+ refetch
146
222
  ]);
147
223
  return /* @__PURE__ */ jsx(ReactiveRootContext.Provider, {
148
224
  value: contextValue,
@@ -1 +1 @@
1
- {"version":3,"file":"page-reactive-data-root.js","names":[],"sources":["../../../src/sdk/data/page-reactive-data-root.tsx"],"sourcesContent":["'use client';\n\nimport type { ErrorLike, OperationVariables, ReactiveVar, TypedDocumentNode } from '@apollo/client';\nimport { useApolloClient, useQuery } from '@apollo/client/react';\nimport { type DocumentNode, Kind, type OperationDefinitionNode } from 'graphql';\nimport { createContext, type ReactNode, useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n// Reactive vars resolvable by name from a Server Component. Functions cannot cross the\n// Server→Client boundary in RSC, so the compiler emits string keys and this component resolves\n// them at runtime. New reactive vars must be registered here to be addressable from generated\n// pages. Currently empty: cart context travels on the transport, so no cart entry is needed\n// (docs/storefront-core/reference-cart.md § Cart context).\nconst REACTIVE_VAR_REGISTRY: Record<string, ReactiveVar<unknown>> = {};\n\n/**\n * Compile-time-stable map keyed by the operation's variable names. Each value is either an Apollo\n * reactive variable (`makeVar`) or the string name of one registered in REACTIVE_VAR_REGISTRY —\n * Server Components must pass strings because functions cannot cross the RSC boundary. Vars that\n * have not yet resolved should hold `null`; the runtime treats both `null` and `undefined` as\n * \"not yet ready\" and keeps `useQuery` skipped.\n */\nexport type ClientVariablesMap<TVars> = Partial<{\n [K in keyof TVars]: ReactiveVar<TVars[K] | null> | string;\n}>;\n\nfunction resolveReactiveVar(value: ReactiveVar<unknown> | string): ReactiveVar<unknown> | undefined {\n if (typeof value === 'function') return value;\n const resolved = REACTIVE_VAR_REGISTRY[value];\n if (!resolved) {\n throw new Error(\n `PageReactiveDataRoot: clientVariables entry \"${value}\" is not registered. Add it to REACTIVE_VAR_REGISTRY in page-reactive-data-root.tsx.`,\n );\n }\n return resolved;\n}\n\nexport type PageReactiveDataRootProps<TData, TVars extends OperationVariables> = Readonly<{\n /** The composed PAGE_QUERY operation document. */\n query: TypedDocumentNode<TData, TVars>;\n /** Variables the server used when calling fetchPageData. Only used to seed the client cache. */\n serverVariables?: TVars;\n /** Server-rendered PAGE_QUERY result. When present and shape-matched, seeded into the cache once. */\n initialData?: TData | null;\n /** Reactive vars whose current values are merged into operation variables at render time. */\n clientVariables?: ClientVariablesMap<TVars>;\n children: ReactNode;\n}>;\n\nexport type ReactiveRootContextValue<TData = unknown> = Readonly<{\n /** Current root query data exposed to descendant `ReactiveDataProvider`s. */\n rootQueryData: TData | undefined;\n /** Whether the underlying useQuery is currently in flight. */\n loading: boolean;\n /** False when at least one required variable has not been provided yet. */\n hasAllRequiredVariables: boolean;\n /**\n * Error from the underlying useQuery. With non-null root fields (e.g. `cart: Cart!`), a failed\n * fetch nulls the whole operation's `data` — descendants need this to tell \"fetch failed\" apart\n * from \"entity is legitimately absent\" (docs/storefront-core/reference-free-particle-model.md § Read side — data).\n */\n error: ErrorLike | undefined;\n /**\n * True only under a real `PageReactiveDataRoot`. Every other field above is ambiguous between\n * \"no root at all\" and \"a root with nothing to give right now\" — `rootQueryData: undefined` and\n * `loading: false` describe both the context default and a root whose query is skipped for want\n * of a reactive variable. `useReactiveEntity` needs that distinction to tell a placeholder slice\n * apart from the only slice there is.\n *\n * Required, not optional, and deliberately so. This flag is the whole of what arms the gate in\n * `useReactiveEntity`, so a provider that omits it disarms the gate silently — measured: a value\n * that `satisfies ReactiveRootContextValue` without it reproduces the corrupting payload byte for\n * byte on an otherwise-fixed tree, and no test fails. `ReactiveRootContext` is public surface via\n * `sdk/data/index.ts`, so that provider need not live in this repo. Making it required moves that\n * failure to compile time.\n *\n * A hand-built context value stands in for a root's *output*, not for the reactive runtime, so it\n * declares `false` and its subtree keeps gating on nothing.\n */\n hasRoot: boolean;\n}>;\n\nexport const ReactiveRootContext = createContext<ReactiveRootContextValue>({\n rootQueryData: undefined,\n loading: false,\n hasAllRequiredVariables: false,\n error: undefined,\n hasRoot: false,\n});\n\nfunction getOperationDefinition(query: DocumentNode): OperationDefinitionNode | undefined {\n for (const definition of query.definitions) {\n if (definition.kind === Kind.OPERATION_DEFINITION) return definition;\n }\n return undefined;\n}\n\nfunction getRequiredVariableNames(query: DocumentNode): string[] {\n const op = getOperationDefinition(query);\n if (!op?.variableDefinitions) return [];\n const required: string[] = [];\n for (const def of op.variableDefinitions) {\n if (def.type.kind === Kind.NON_NULL_TYPE && def.defaultValue === undefined) {\n required.push(def.variable.name.value);\n }\n }\n return required;\n}\n\nfunction getTopLevelFieldNames(query: DocumentNode): Set<string> {\n const op = getOperationDefinition(query);\n const names = new Set<string>();\n if (!op) return names;\n for (const sel of op.selectionSet.selections) {\n if (sel.kind === Kind.FIELD) {\n names.add((sel.alias ?? sel.name).value);\n }\n }\n return names;\n}\n\nfunction shapeMatches(query: DocumentNode, data: unknown): boolean {\n if (data == null || typeof data !== 'object') return false;\n const expected = getTopLevelFieldNames(query);\n if (expected.size === 0) return false;\n const actual = data as Record<string, unknown>;\n // Require AT LEAST ONE top-level field name to be present in `data`. The operation's full\n // top-level shape may include fields the server did not return; partial seeds still help. A total\n // miss (no overlap at all) signals a shape mismatch and we skip the seed.\n for (const key of expected) {\n if (key in actual) return true;\n }\n return false;\n}\n\n/**\n * Reads the current values of every reactive var in `clientVariables` into a plain object via a\n * single `useSyncExternalStore` call (no hooks-in-loop). Apollo's `ReactiveVar.onNextChange` fires\n * once per registration, so the subscriber re-attaches after each notification to stay listening.\n */\nfunction useClientVariableValues<TVars>(clientVariables: ClientVariablesMap<TVars> | undefined): Partial<TVars> {\n // Keep the latest map identity reachable from inside subscribe()/getSnapshot() without forcing\n // useSyncExternalStore to tear down the underlying subscription.\n const varsRef = useRef(clientVariables);\n // react-doctor-disable-next-line no-ref-current-in-render -- keeps the latest map reachable from the stable subscribe()/getSnapshot() callbacks below\n varsRef.current = clientVariables;\n\n const subscribe = useCallback((notify: () => void): (() => void) => {\n const cv = varsRef.current;\n if (!cv) return () => {};\n\n const activeUnsubs: Array<() => void> = [];\n let cancelled = false;\n\n const attach = (rv: ReactiveVar<unknown>): void => {\n if (cancelled) return;\n const unsub = rv.onNextChange(() => {\n notify();\n // onNextChange fires only once — re-attach to keep listening for subsequent changes.\n attach(rv);\n });\n activeUnsubs.push(unsub);\n };\n\n for (const rv of Object.values(cv)) {\n if (rv === undefined || rv === null) continue;\n const resolved = resolveReactiveVar(rv as ReactiveVar<unknown> | string);\n if (resolved) attach(resolved);\n }\n\n return () => {\n cancelled = true;\n for (const u of activeUnsubs) u();\n };\n }, []);\n\n const lastSnapshotRef = useRef<{ json: string; value: Record<string, unknown> } | null>(null);\n\n const getSnapshot = (): string => {\n const cv = varsRef.current;\n const snap: Record<string, unknown> = {};\n if (cv) {\n for (const [key, reactiveVar] of Object.entries(cv)) {\n if (reactiveVar === undefined || reactiveVar === null) continue;\n const resolved = resolveReactiveVar(reactiveVar as ReactiveVar<unknown> | string);\n if (!resolved) continue;\n const value = resolved();\n if (value !== null && value !== undefined) snap[key] = value;\n }\n }\n const json = JSON.stringify(snap);\n // useSyncExternalStore expects a stable snapshot reference until the value changes (Object.is\n // identity check) — return the cached `json` whenever the contents are unchanged.\n if (lastSnapshotRef.current && lastSnapshotRef.current.json === json) {\n return lastSnapshotRef.current.json;\n }\n lastSnapshotRef.current = { json, value: snap };\n return json;\n };\n\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n return (lastSnapshotRef.current?.value ?? {}) as Partial<TVars>;\n}\n\nexport function PageReactiveDataRoot<TData = unknown, TVars extends OperationVariables = OperationVariables>(\n props: PageReactiveDataRootProps<TData, TVars>,\n): ReactNode {\n const { query, serverVariables, initialData, clientVariables, children } = props;\n const client = useApolloClient();\n const seededRef = useRef(false);\n\n const clientValues = useClientVariableValues<TVars>(clientVariables);\n const mergedVariables = { ...(serverVariables ?? ({} as TVars)), ...clientValues } as TVars;\n\n const requiredNames = getRequiredVariableNames(query);\n const mergedRecord = mergedVariables as Record<string, unknown>;\n const hasAllRequiredVariables = requiredNames.every((name) => {\n const v = mergedRecord[name];\n return v !== null && v !== undefined;\n });\n\n // Ref- and shape-guarded one-time cache seed from server props. We use `useEffect` (not\n // `useLayoutEffect`) to avoid the SSR-pass warning emitted by React 19 — the resulting \"first\n // render shows fallbackData, second render shows cache slice\" behaviour is acceptable because\n // initialData is the same value `ReactiveDataProvider` falls back to anyway.\n useEffect(() => {\n if (seededRef.current) return;\n if (!serverVariables || initialData == null) return;\n if (!shapeMatches(query, initialData)) return;\n client.cache.writeQuery({ query, variables: serverVariables, data: initialData });\n seededRef.current = true;\n }, [client, query, serverVariables, initialData]);\n\n const { data, loading, error } = useQuery(query, {\n variables: mergedVariables,\n fetchPolicy: 'cache-first',\n skip: !hasAllRequiredVariables,\n });\n\n const contextValue: ReactiveRootContextValue<TData> = useMemo(\n () => ({\n rootQueryData: (data ?? undefined) as TData | undefined,\n loading,\n hasAllRequiredVariables,\n error,\n hasRoot: true,\n }),\n [data, loading, hasAllRequiredVariables, error],\n );\n\n return (\n <ReactiveRootContext.Provider value={contextValue as ReactiveRootContextValue}>\n {children}\n </ReactiveRootContext.Provider>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"page-reactive-data-root.js","names":[],"sources":["../../../src/sdk/data/page-reactive-data-root.tsx"],"sourcesContent":["'use client';\n\nimport type {\n ErrorLike,\n OperationVariables,\n ReactiveVar,\n TypedDocumentNode,\n WatchQueryFetchPolicy,\n} from '@apollo/client';\nimport { useApolloClient, useQuery } from '@apollo/client/react';\nimport { canonicalStringify } from '@apollo/client/utilities';\nimport { type DocumentNode, Kind, type OperationDefinitionNode } from 'graphql';\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { getFailedFields } from './failed-fields.js';\n\n// Reactive vars resolvable by name from a Server Component. Functions cannot cross the\n// Server→Client boundary in RSC, so the compiler emits string keys and this component resolves\n// them at runtime. New reactive vars must be registered here to be addressable from generated\n// pages. Currently empty: cart context travels on the transport, so no cart entry is needed\n// (docs/storefront-core/reference-cart.md § Cart context).\nconst REACTIVE_VAR_REGISTRY: Record<string, ReactiveVar<unknown>> = {};\n\n/**\n * Compile-time-stable map keyed by the operation's variable names. Each value is either an Apollo\n * reactive variable (`makeVar`) or the string name of one registered in REACTIVE_VAR_REGISTRY —\n * Server Components must pass strings because functions cannot cross the RSC boundary. Vars that\n * have not yet resolved should hold `null`; the runtime treats both `null` and `undefined` as\n * \"not yet ready\" and keeps `useQuery` skipped.\n */\nexport type ClientVariablesMap<TVars> = Partial<{\n [K in keyof TVars]: ReactiveVar<TVars[K] | null> | string;\n}>;\n\nfunction resolveReactiveVar(value: ReactiveVar<unknown> | string): ReactiveVar<unknown> | undefined {\n if (typeof value === 'function') return value;\n const resolved = REACTIVE_VAR_REGISTRY[value];\n if (!resolved) {\n throw new Error(\n `PageReactiveDataRoot: clientVariables entry \"${value}\" is not registered. Add it to REACTIVE_VAR_REGISTRY in page-reactive-data-root.tsx.`,\n );\n }\n return resolved;\n}\n\nexport type PageReactiveDataRootProps<TData, TVars extends OperationVariables> = Readonly<{\n /** The composed PAGE_QUERY operation document. */\n query: TypedDocumentNode<TData, TVars>;\n /** Variables the server used when calling fetchPageData. Only used to seed the client cache. */\n serverVariables?: TVars;\n /** Server-rendered PAGE_QUERY result. When present and shape-matched, seeded into the cache once. */\n initialData?: TData | null;\n /**\n * Root fields that failed on the server. Non-empty: `initialData` is not seeded and the first read\n * for these `serverVariables` is `network-only`. Read on mount and when `serverVariables` change;\n * a change to `failedFields` alone, with the same variables, sends nothing.\n */\n failedFields?: readonly string[];\n /** Reactive vars whose current values are merged into operation variables at render time. */\n clientVariables?: ClientVariablesMap<TVars>;\n children: ReactNode;\n}>;\n\nexport type ReactiveRootContextValue<TData = unknown> = Readonly<{\n /** Current root query data exposed to descendant `ReactiveDataProvider`s. */\n rootQueryData: TData | undefined;\n /** Whether the underlying useQuery is currently in flight. */\n loading: boolean;\n /** False when at least one required variable has not been provided yet. */\n hasAllRequiredVariables: boolean;\n /**\n * Error from the underlying useQuery. With non-null root fields (e.g. `cart: Cart!`), a failed\n * fetch nulls the whole operation's `data` — descendants need this to tell \"fetch failed\" apart\n * from \"entity is legitimately absent\" (docs/storefront-core/reference-free-particle-model.md § Read side — data).\n * A nullable root field that failed on the server reaches here too: the root skips the seed and\n * asks again, and this carries the browser's error beside the partial data.\n */\n error: ErrorLike | undefined;\n /**\n * True only under a real `PageReactiveDataRoot`. Every other field above is ambiguous between\n * \"no root at all\" and \"a root with nothing to give right now\" — `rootQueryData: undefined` and\n * `loading: false` describe both the context default and a root whose query is skipped for want\n * of a reactive variable. `useReactiveEntity` needs that distinction to tell a placeholder slice\n * apart from the only slice there is.\n *\n * Required, not optional, and deliberately so. This flag is the whole of what arms the gate in\n * `useReactiveEntity`, so a provider that omits it disarms the gate silently — measured: a value\n * that `satisfies ReactiveRootContextValue` without it reproduces the corrupting payload byte for\n * byte on an otherwise-fixed tree, and no test fails. `ReactiveRootContext` ships through the\n * package's `./internal/client` entry, so that provider need not live in this repo. Making it\n * required moves that failure to compile time.\n *\n * A hand-built context value stands in for a root's *output*, not for the reactive runtime, so it\n * declares `false` and its subtree keeps gating on nothing.\n */\n hasRoot: boolean;\n /**\n * Re-runs the page query against the network with the root's current variables, the ones its\n * latest render passed to `useQuery`, so a caller that changes a reactive variable calls it from an\n * effect, not in the same handler. Sends nothing unless the root's last committed render had every\n * required variable, nor after the root unmounted. Never rejects: a failure of the read arrives on\n * `rootQueryData`, `loading`, and `error`, as it does on a first load, a cancelled refetch is\n * silent, and any other failure is logged by its name and swallowed.\n *\n * Optional, unlike `hasRoot`: a provider that omits it only loses the in-place retry, where a\n * provider that omits `hasRoot` corrupts what `useReactiveEntity` builds. `PageReactiveDataRoot`\n * always sets it, but a provider built outside this repo may not, and a caller then falls back to\n * reloading the page.\n */\n refetch?: () => Promise<void>;\n}>;\n\nexport const ReactiveRootContext = createContext<ReactiveRootContextValue>({\n rootQueryData: undefined,\n loading: false,\n hasAllRequiredVariables: false,\n error: undefined,\n hasRoot: false,\n});\n\nfunction getOperationDefinition(query: DocumentNode): OperationDefinitionNode | undefined {\n for (const definition of query.definitions) {\n if (definition.kind === Kind.OPERATION_DEFINITION) return definition;\n }\n return undefined;\n}\n\nfunction getRequiredVariableNames(query: DocumentNode): string[] {\n const op = getOperationDefinition(query);\n if (!op?.variableDefinitions) return [];\n const required: string[] = [];\n for (const def of op.variableDefinitions) {\n if (def.type.kind === Kind.NON_NULL_TYPE && def.defaultValue === undefined) {\n required.push(def.variable.name.value);\n }\n }\n return required;\n}\n\nfunction getTopLevelFieldNames(query: DocumentNode): Set<string> {\n const op = getOperationDefinition(query);\n const names = new Set<string>();\n if (!op) return names;\n for (const sel of op.selectionSet.selections) {\n if (sel.kind === Kind.FIELD) {\n names.add((sel.alias ?? sel.name).value);\n }\n }\n return names;\n}\n\nfunction stillShowsFailure(data: unknown, error: ErrorLike): boolean {\n return data == null || getFailedFields(data, error).length > 0;\n}\n\nfunction withEntry(\n errors: ReadonlyMap<string, ErrorLike>,\n key: string,\n error: ErrorLike | undefined,\n): ReadonlyMap<string, ErrorLike> {\n if (errors.get(key) === error) return errors;\n const next = new Map(errors);\n if (error) next.set(key, error);\n else next.delete(key);\n return next;\n}\n\nfunction shapeMatches(query: DocumentNode, data: unknown): boolean {\n if (data == null || typeof data !== 'object') return false;\n const expected = getTopLevelFieldNames(query);\n if (expected.size === 0) return false;\n const actual = data as Record<string, unknown>;\n // Require AT LEAST ONE top-level field name to be present in `data`. The operation's full\n // top-level shape may include fields the server did not return; partial seeds still help. A total\n // miss (no overlap at all) signals a shape mismatch and we skip the seed.\n for (const key of expected) {\n if (key in actual) return true;\n }\n return false;\n}\n\n/**\n * Reads the current values of every reactive var in `clientVariables` into a plain object via a\n * single `useSyncExternalStore` call (no hooks-in-loop). Apollo's `ReactiveVar.onNextChange` fires\n * once per registration, so the subscriber re-attaches after each notification to stay listening.\n */\nfunction useClientVariableValues<TVars>(clientVariables: ClientVariablesMap<TVars> | undefined): Partial<TVars> {\n // Keep the latest map identity reachable from inside subscribe()/getSnapshot() without forcing\n // useSyncExternalStore to tear down the underlying subscription.\n const varsRef = useRef(clientVariables);\n // react-doctor-disable-next-line no-ref-current-in-render -- keeps the latest map reachable from the stable subscribe()/getSnapshot() callbacks below\n varsRef.current = clientVariables;\n\n const subscribe = useCallback((notify: () => void): (() => void) => {\n const cv = varsRef.current;\n if (!cv) return () => {};\n\n const activeUnsubs: Array<() => void> = [];\n let cancelled = false;\n\n const attach = (rv: ReactiveVar<unknown>): void => {\n if (cancelled) return;\n const unsub = rv.onNextChange(() => {\n notify();\n // onNextChange fires only once — re-attach to keep listening for subsequent changes.\n attach(rv);\n });\n activeUnsubs.push(unsub);\n };\n\n for (const rv of Object.values(cv)) {\n if (rv === undefined || rv === null) continue;\n const resolved = resolveReactiveVar(rv as ReactiveVar<unknown> | string);\n if (resolved) attach(resolved);\n }\n\n return () => {\n cancelled = true;\n for (const u of activeUnsubs) u();\n };\n }, []);\n\n const lastSnapshotRef = useRef<{ json: string; value: Record<string, unknown> } | null>(null);\n\n const getSnapshot = (): string => {\n const cv = varsRef.current;\n const snap: Record<string, unknown> = {};\n if (cv) {\n for (const [key, reactiveVar] of Object.entries(cv)) {\n if (reactiveVar === undefined || reactiveVar === null) continue;\n const resolved = resolveReactiveVar(reactiveVar as ReactiveVar<unknown> | string);\n if (!resolved) continue;\n const value = resolved();\n if (value !== null && value !== undefined) snap[key] = value;\n }\n }\n const json = JSON.stringify(snap);\n // useSyncExternalStore expects a stable snapshot reference until the value changes (Object.is\n // identity check) — return the cached `json` whenever the contents are unchanged.\n if (lastSnapshotRef.current && lastSnapshotRef.current.json === json) {\n return lastSnapshotRef.current.json;\n }\n lastSnapshotRef.current = { json, value: snap };\n return json;\n };\n\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n return (lastSnapshotRef.current?.value ?? {}) as Partial<TVars>;\n}\n\n/**\n * Apollo rejects a refetch that ends with no value, as when the root unmounts mid-flight, with a\n * `DOMException` named `AbortError`, or a plain `Error` of that name where there is no `DOMException`.\n * A property check, since a `DOMException` is not always an `Error` instance (jsdom). The read is\n * guarded because a `.name` getter can throw, and `refetch` must not reject.\n */\nfunction isAbort(failure: unknown): boolean {\n if (typeof failure !== 'object' || failure === null) return false;\n try {\n return (failure as { name?: unknown }).name === 'AbortError';\n } catch {\n return false;\n }\n}\n\nexport function PageReactiveDataRoot<TData = unknown, TVars extends OperationVariables = OperationVariables>({\n query,\n serverVariables,\n initialData,\n failedFields,\n clientVariables,\n children,\n}: PageReactiveDataRootProps<TData, TVars>): ReactNode {\n const client = useApolloClient();\n const seededKeysRef = useRef(new Set<string>());\n const hasFailedFields = failedFields != null && failedFields.length > 0;\n const serverVariablesKey = canonicalStringify(serverVariables ?? null);\n\n const clientValues = useClientVariableValues<TVars>(clientVariables);\n const mergedVariables = { ...(serverVariables ?? ({} as TVars)), ...clientValues } as TVars;\n\n const requiredNames = getRequiredVariableNames(query);\n const mergedRecord = mergedVariables as Record<string, unknown>;\n const hasAllRequiredVariables = requiredNames.every((name) => {\n const v = mergedRecord[name];\n return v !== null && v !== undefined;\n });\n\n // Ref- and shape-guarded cache seed from server props, once per set of server variables. A passive\n // effect is enough: the resulting \"first render shows fallbackData, second render shows cache\n // slice\" behaviour is acceptable because initialData is the same value `ReactiveDataProvider`\n // falls back to anyway.\n //\n // A result with failed root fields is never seeded: the seed would store each failed field's\n // `null` without its error, and the cache-first read would then report the failure as an absence.\n // Keying on the variables lets a healthy render replace the `null` an earlier failed read left.\n useEffect(() => {\n if (seededKeysRef.current.has(serverVariablesKey)) return;\n if (hasFailedFields) return;\n if (!serverVariables || initialData == null) return;\n if (!shapeMatches(query, initialData)) return;\n client.cache.writeQuery({ query, variables: serverVariables, data: initialData });\n seededKeysRef.current.add(serverVariablesKey);\n }, [client, query, serverVariables, serverVariablesKey, initialData, hasFailedFields]);\n\n // `network-only` on a failed field: the client cache outlives the page, and Apollo caches a result\n // with errors under `errorPolicy: 'all'`, so an earlier failed request would otherwise answer a\n // revisit from the cache with no error. `errorPolicy` is pinned so the partial data survives\n // beside the error whatever the client's defaults.\n //\n // `useQuery` re-sends `fetchPolicy` on every render, and on a variables change Apollo lets a\n // `nextFetchPolicy` function overrule it. The function sends one network read per set of server\n // variables with failed fields, so a client-variable change reads the cache as on a healthy page.\n const networkReadKeysRef = useRef(new Set<string>());\n const nextFetchPolicy = useCallback(\n (\n _current: WatchQueryFetchPolicy,\n { reason }: { reason: 'after-fetch' | 'variables-changed' },\n ): WatchQueryFetchPolicy => {\n if (!hasFailedFields) return 'cache-first';\n if (reason === 'variables-changed' && !networkReadKeysRef.current.has(serverVariablesKey)) return 'network-only';\n networkReadKeysRef.current.add(serverVariablesKey);\n return 'cache-first';\n },\n [hasFailedFields, serverVariablesKey],\n );\n\n const {\n data,\n loading,\n error,\n refetch: refetchQuery,\n } = useQuery(query, {\n variables: mergedVariables,\n fetchPolicy: hasFailedFields ? 'network-only' : 'cache-first',\n nextFetchPolicy,\n errorPolicy: 'all',\n skip: !hasAllRequiredVariables,\n });\n\n // Apollo never caches errors: a cache write that re-delivers the result drops `error` and leaves\n // the failed field's `null`. The root keeps the last network error per set of variables while the\n // data still shows that failure; a new request for those variables, or a write that heals the\n // field, drops it.\n const variablesKey = canonicalStringify(mergedVariables);\n const [networkErrors, setNetworkErrors] = useState<ReadonlyMap<string, ErrorLike>>(() => new Map());\n const storedError = networkErrors.get(variablesKey);\n const storedErrorApplies = storedError !== undefined && !loading && stillShowsFailure(data, storedError);\n useEffect(() => {\n if (error) {\n setNetworkErrors((errors) => withEntry(errors, variablesKey, error));\n } else if (loading || (storedError !== undefined && !storedErrorApplies)) {\n setNetworkErrors((errors) => withEntry(errors, variablesKey, undefined));\n }\n }, [error, loading, variablesKey, storedError, storedErrorApplies]);\n const rootError = error ?? (storedErrorApplies ? storedError : undefined);\n\n // Apollo binds `refetch` once per ObservableQuery, so it changes when the query document or the\n // client changes. The published one reads the latest through this ref, so its identity holds across\n // those swaps. The ref is written once a render commits, never during one: a render React abandons\n // must not leak its readiness or handle into a caller. It is written in a layout effect so it is\n // set before any descendant's passive effect calls `refetch`. It is null once the root unmounted,\n // because refetching then would revive a torn-down ObservableQuery that nothing is left to stop.\n const latestRef = useRef<{ refetchQuery: typeof refetchQuery; hasAllRequiredVariables: boolean } | null>(null);\n useLayoutEffect(() => {\n latestRef.current = { refetchQuery, hasAllRequiredVariables };\n });\n useLayoutEffect(\n () => () => {\n latestRef.current = null;\n },\n [],\n );\n\n const refetch = useCallback(async (): Promise<void> => {\n const latest = latestRef.current;\n if (!latest?.hasAllRequiredVariables) return;\n try {\n await latest.refetchQuery();\n } catch (error_) {\n // Under the pinned `errorPolicy: 'all'` a failed read resolves and reaches descendants through\n // `error`. A cancelled refetch rejects and needs no report. Any other failure (a local-state\n // error, an Apollo invariant) never reaches `error`, so it is logged by name: no caller handles\n // a rejection.\n if (!isAbort(error_)) console.error('[PageReactiveDataRoot] refetch failed', loggableErrorName(error_));\n }\n }, []);\n\n const contextValue: ReactiveRootContextValue<TData> = useMemo(\n () => ({\n rootQueryData: (data ?? undefined) as TData | undefined,\n loading,\n hasAllRequiredVariables,\n error: rootError,\n hasRoot: true,\n refetch,\n }),\n [data, loading, hasAllRequiredVariables, rootError, refetch],\n );\n\n return (\n <ReactiveRootContext.Provider value={contextValue as ReactiveRootContextValue}>\n {children}\n </ReactiveRootContext.Provider>\n );\n}\n"],"mappings":"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"}