@vtex/faststore 0.0.7 → 0.0.8

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 (411) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/actions.json +152 -1
  3. package/dist/catalog.json +1264 -142
  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 +34 -7
  12. package/dist/components.js +38 -11
  13. package/dist/content-types.json +523 -0
  14. package/dist/core/dist/define-component.d.ts +6 -1
  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 +1 -1
  18. package/dist/docs/account-menu.md +5 -5
  19. package/dist/docs/alert-dialog.md +2 -2
  20. package/dist/docs/billing-address.md +17 -9
  21. package/dist/docs/card-entry.md +37 -6
  22. package/dist/docs/cardholder-document.md +16 -14
  23. package/dist/docs/cart-item.md +1 -1
  24. package/dist/docs/cart.md +8 -8
  25. package/dist/docs/client-profile-fieldset.md +7 -5
  26. package/dist/docs/copy-text.md +84 -0
  27. package/dist/docs/document-field.md +8 -8
  28. package/dist/docs/footer-link-group.md +2 -2
  29. package/dist/docs/header-root.md +1 -1
  30. package/dist/docs/incentives.md +2 -2
  31. package/dist/docs/logo.md +5 -5
  32. package/dist/docs/my-account-profile.md +4 -4
  33. package/dist/docs/navigation-link-list.md +2 -2
  34. package/dist/docs/navigation-link.md +3 -3
  35. package/dist/docs/order-cancel.md +52 -0
  36. package/dist/docs/order-delivery-address.md +63 -0
  37. package/dist/docs/order-delivery-group.md +83 -0
  38. package/dist/docs/order-details-back.md +41 -0
  39. package/dist/docs/order-details-header.md +48 -0
  40. package/dist/docs/order-list-row.md +46 -0
  41. package/dist/docs/order-list-search.md +101 -0
  42. package/dist/docs/order-list-views.md +97 -0
  43. package/dist/docs/order-list.md +94 -0
  44. package/dist/docs/order-payment.md +94 -0
  45. package/dist/docs/order-placed-date.md +49 -0
  46. package/dist/docs/order-placed-payment-summary.md +13 -15
  47. package/dist/docs/order-placed.md +225 -0
  48. package/dist/docs/order-progress.md +88 -0
  49. package/dist/docs/order-status-badge.md +91 -0
  50. package/dist/docs/order-summary.md +53 -0
  51. package/dist/docs/payment-method-badges.md +14 -3
  52. package/dist/docs/place-order.md +13 -9
  53. package/dist/docs/saved-cards.md +2 -2
  54. package/dist/docs/select.md +19 -6
  55. package/dist/docs/session.md +1 -1
  56. package/dist/docs/shipping-address.md +1 -1
  57. package/dist/docs/sign-in-redirect.md +6 -6
  58. package/dist/docs/toast.md +7 -3
  59. package/dist/index.d.ts +6 -2
  60. package/dist/index.js +2 -1
  61. package/dist/internal/client.d.ts +6 -1
  62. package/dist/internal/client.js +7 -2
  63. package/dist/internal/server.d.ts +6 -2
  64. package/dist/internal/server.js +6 -2
  65. package/dist/matter/account-access-gate/account-access-gate.d.ts +2 -1
  66. package/dist/matter/account-access-gate/account-access-gate.js +19 -16
  67. package/dist/matter/account-access-gate/account-access-gate.js.map +1 -1
  68. package/dist/matter/account-address-book/account-address-book-delete.d.ts +60 -0
  69. package/dist/matter/account-address-book/account-address-book-delete.js +184 -0
  70. package/dist/matter/account-address-book/account-address-book-delete.js.map +1 -0
  71. package/dist/matter/account-address-book/account-address-book-deliverability.d.ts +19 -0
  72. package/dist/matter/account-address-book/account-address-book-deliverability.js +50 -0
  73. package/dist/matter/account-address-book/account-address-book-deliverability.js.map +1 -0
  74. package/dist/matter/account-address-book/account-address-book-form-parts.d.ts +42 -0
  75. package/dist/matter/account-address-book/account-address-book-form-parts.js +146 -0
  76. package/dist/matter/account-address-book/account-address-book-form-parts.js.map +1 -0
  77. package/dist/matter/account-address-book/account-address-book-profile-notice.d.ts +25 -0
  78. package/dist/matter/account-address-book/account-address-book-profile-notice.js +72 -0
  79. package/dist/matter/account-address-book/account-address-book-profile-notice.js.map +1 -0
  80. package/dist/matter/account-address-book/account-address-book.d.ts +120 -0
  81. package/dist/matter/account-address-book/account-address-book.js +469 -0
  82. package/dist/matter/account-address-book/account-address-book.js.map +1 -0
  83. package/dist/matter/account-address-book/account-address-country-switch.js +59 -0
  84. package/dist/matter/account-address-book/account-address-country-switch.js.map +1 -0
  85. package/dist/matter/account-address-book/account-address-country.d.ts +36 -0
  86. package/dist/matter/account-address-book/account-address-country.js +151 -0
  87. package/dist/matter/account-address-book/account-address-country.js.map +1 -0
  88. package/dist/matter/account-address-book/account-address-first-line.js +11 -0
  89. package/dist/matter/account-address-book/account-address-first-line.js.map +1 -0
  90. package/dist/matter/account-address-book/account-address-form-context.d.ts +55 -0
  91. package/dist/matter/account-address-book/account-address-form-context.js +14 -0
  92. package/dist/matter/account-address-book/account-address-form-context.js.map +1 -0
  93. package/dist/matter/account-address-book/account-address-form.d.ts +85 -0
  94. package/dist/matter/account-address-book/account-address-form.js +946 -0
  95. package/dist/matter/account-address-book/account-address-form.js.map +1 -0
  96. package/dist/matter/account-address-book/account-address-postal-autofill.js +189 -0
  97. package/dist/matter/account-address-book/account-address-postal-autofill.js.map +1 -0
  98. package/dist/matter/account-address-book/account-address-record.d.ts +26 -0
  99. package/dist/matter/account-address-book/account-address-record.js +138 -0
  100. package/dist/matter/account-address-book/account-address-record.js.map +1 -0
  101. package/dist/matter/account-entry-point/account-entry-point.js +11 -11
  102. package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
  103. package/dist/matter/account-menu/account-menu.d.ts +7 -3
  104. package/dist/matter/account-menu/account-menu.js +69 -28
  105. package/dist/matter/account-menu/account-menu.js.map +1 -1
  106. package/dist/matter/billing-address/billing-address.d.ts +30 -17
  107. package/dist/matter/billing-address/billing-address.js +128 -33
  108. package/dist/matter/billing-address/billing-address.js.map +1 -1
  109. package/dist/matter/breadcrumb/breadcrumb.js +2 -2
  110. package/dist/matter/breadcrumb/breadcrumb.js.map +1 -1
  111. package/dist/matter/card-entry/card-capture.js +4 -3
  112. package/dist/matter/card-entry/card-capture.js.map +1 -1
  113. package/dist/matter/card-entry/card-entry.d.ts +5 -3
  114. package/dist/matter/card-entry/card-entry.js +77 -27
  115. package/dist/matter/card-entry/card-entry.js.map +1 -1
  116. package/dist/matter/cardholder-document/cardholder-document.d.ts +10 -9
  117. package/dist/matter/cardholder-document/cardholder-document.js +36 -16
  118. package/dist/matter/cardholder-document/cardholder-document.js.map +1 -1
  119. package/dist/matter/cart/cart.d.ts +7 -2
  120. package/dist/matter/cart/cart.js +29 -26
  121. package/dist/matter/cart/cart.js.map +1 -1
  122. package/dist/matter/cart-item/cart-item.js +2 -2
  123. package/dist/matter/cart-item/cart-item.js.map +1 -1
  124. package/dist/matter/client-preferences/client-preferences-fieldset.js +1 -1
  125. package/dist/matter/client-profile-form/client-profile-fieldset.js +8 -5
  126. package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -1
  127. package/dist/matter/identity-edit/identity-edit.js +3 -3
  128. package/dist/matter/identity-edit/identity-edit.js.map +1 -1
  129. package/dist/matter/identity-summary/identity-summary.js +2 -2
  130. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  131. package/dist/matter/my-account-profile/my-account-profile.d.ts +3 -3
  132. package/dist/matter/my-account-profile/my-account-profile.js +4 -4
  133. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
  134. package/dist/matter/order-cancel/order-cancel.d.ts +110 -0
  135. package/dist/matter/order-cancel/order-cancel.js +344 -0
  136. package/dist/matter/order-cancel/order-cancel.js.map +1 -0
  137. package/dist/matter/order-delivery-address/order-delivery-address.d.ts +73 -0
  138. package/dist/matter/order-delivery-address/order-delivery-address.js +172 -0
  139. package/dist/matter/order-delivery-address/order-delivery-address.js.map +1 -0
  140. package/dist/matter/order-delivery-group/order-delivery-group.d.ts +128 -0
  141. package/dist/matter/order-delivery-group/order-delivery-group.js +408 -0
  142. package/dist/matter/order-delivery-group/order-delivery-group.js.map +1 -0
  143. package/dist/matter/order-details-header/order-details-header.d.ts +104 -0
  144. package/dist/matter/order-details-header/order-details-header.js +190 -0
  145. package/dist/matter/order-details-header/order-details-header.js.map +1 -0
  146. package/dist/matter/order-list/customer-orders-filter.d.ts +12 -0
  147. package/dist/matter/order-list/customer-orders-filter.js +30 -0
  148. package/dist/matter/order-list/customer-orders-filter.js.map +1 -0
  149. package/dist/matter/order-list/order-list.d.ts +121 -0
  150. package/dist/matter/order-list/order-list.js +395 -0
  151. package/dist/matter/order-list/order-list.js.map +1 -0
  152. package/dist/matter/order-list-row/order-list-row.d.ts +44 -0
  153. package/dist/matter/order-list-row/order-list-row.js +127 -0
  154. package/dist/matter/order-list-row/order-list-row.js.map +1 -0
  155. package/dist/matter/order-list-search/order-list-search.d.ts +72 -0
  156. package/dist/matter/order-list-search/order-list-search.js +296 -0
  157. package/dist/matter/order-list-search/order-list-search.js.map +1 -0
  158. package/dist/matter/order-list-views/order-list-views.d.ts +65 -0
  159. package/dist/matter/order-list-views/order-list-views.js +154 -0
  160. package/dist/matter/order-list-views/order-list-views.js.map +1 -0
  161. package/dist/matter/order-payment/order-payment.d.ts +83 -0
  162. package/dist/matter/order-payment/order-payment.js +258 -0
  163. package/dist/matter/order-payment/order-payment.js.map +1 -0
  164. package/dist/matter/order-placed/order-placed-deliveries.d.ts +145 -0
  165. package/dist/matter/order-placed/order-placed-deliveries.js +380 -0
  166. package/dist/matter/order-placed/order-placed-deliveries.js.map +1 -0
  167. package/dist/matter/order-placed/order-placed.d.ts +122 -0
  168. package/dist/matter/order-placed/order-placed.js +452 -0
  169. package/dist/matter/order-placed/order-placed.js.map +1 -0
  170. package/dist/matter/order-placed-date/order-placed-date.d.ts +45 -0
  171. package/dist/matter/order-placed-date/order-placed-date.js +92 -0
  172. package/dist/matter/order-placed-date/order-placed-date.js.map +1 -0
  173. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +5 -8
  174. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +29 -81
  175. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -1
  176. package/dist/matter/order-progress/order-progress.d.ts +85 -0
  177. package/dist/matter/order-progress/order-progress.js +203 -0
  178. package/dist/matter/order-progress/order-progress.js.map +1 -0
  179. package/dist/matter/order-summary/order-summary.d.ts +81 -0
  180. package/dist/matter/order-summary/order-summary.js +192 -0
  181. package/dist/matter/order-summary/order-summary.js.map +1 -0
  182. package/dist/matter/payment/cardholder-document-rule.d.ts +35 -0
  183. package/dist/matter/payment/cardholder-document-rule.js +35 -0
  184. package/dist/matter/payment/cardholder-document-rule.js.map +1 -0
  185. package/dist/matter/payment/payment-card-capture.js +38 -23
  186. package/dist/matter/payment/payment-card-capture.js.map +1 -1
  187. package/dist/matter/payment/payment-controller.js +11 -0
  188. package/dist/matter/payment/payment-controller.js.map +1 -1
  189. package/dist/matter/payment/payment-method-badges.js +4 -2
  190. package/dist/matter/payment/payment-method-badges.js.map +1 -1
  191. package/dist/matter/payment/payment.js +11 -0
  192. package/dist/matter/payment/payment.js.map +1 -1
  193. package/dist/matter/payment/use-payment-method-selection.d.ts +39 -0
  194. package/dist/matter/payment/use-payment-method-selection.js +30 -2
  195. package/dist/matter/payment/use-payment-method-selection.js.map +1 -1
  196. package/dist/matter/payment/use-payment-submit-gate.d.ts +16 -2
  197. package/dist/matter/payment/use-payment-submit-gate.js +42 -14
  198. package/dist/matter/payment/use-payment-submit-gate.js.map +1 -1
  199. package/dist/matter/place-order/place-order.d.ts +12 -7
  200. package/dist/matter/place-order/place-order.js +126 -103
  201. package/dist/matter/place-order/place-order.js.map +1 -1
  202. package/dist/matter/pricing/pricing.js +5 -5
  203. package/dist/matter/pricing/pricing.js.map +1 -1
  204. package/dist/matter/region/region.js +1 -1
  205. package/dist/matter/saved-cards/saved-cards.js +26 -15
  206. package/dist/matter/saved-cards/saved-cards.js.map +1 -1
  207. package/dist/matter/session/session.js +2 -2
  208. package/dist/matter/session/session.js.map +1 -1
  209. package/dist/matter/shipping-address/country-descriptors.d.ts +1 -0
  210. package/dist/matter/shipping-address/country-descriptors.js +23 -1
  211. package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
  212. package/dist/matter/shipping-address/shipping-address.js +62 -14
  213. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  214. package/dist/matter/shipping-simulator/shipping-simulator.d.ts +1 -1
  215. package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
  216. package/dist/matter/sign-in-redirect/sign-in-redirect.js +48 -10
  217. package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -1
  218. package/dist/matter/variant-selector/variant-selector.js +2 -2
  219. package/dist/matter/variant-selector/variant-selector.js.map +1 -1
  220. package/dist/particles/address-summary/address-summary.js +2 -2
  221. package/dist/particles/address-summary/address-summary.js.map +1 -1
  222. package/dist/particles/alert-dialog/alert-dialog.d.ts +5 -1
  223. package/dist/particles/alert-dialog/alert-dialog.js +5 -2
  224. package/dist/particles/alert-dialog/alert-dialog.js.map +1 -1
  225. package/dist/particles/copy-text/copy-text.d.ts +28 -0
  226. package/dist/particles/copy-text/copy-text.js +140 -0
  227. package/dist/particles/copy-text/copy-text.js.map +1 -0
  228. package/dist/particles/document-field/document-field.d.ts +34 -5
  229. package/dist/particles/document-field/document-field.js +115 -19
  230. package/dist/particles/document-field/document-field.js.map +1 -1
  231. package/dist/particles/field/field.js +1 -1
  232. package/dist/particles/field/field.js.map +1 -1
  233. package/dist/particles/footer-link-group/footer-link-group.d.ts +6 -2
  234. package/dist/particles/footer-link-group/footer-link-group.js +18 -7
  235. package/dist/particles/footer-link-group/footer-link-group.js.map +1 -1
  236. package/dist/particles/incentives/incentives.d.ts +6 -2
  237. package/dist/particles/incentives/incentives.js +7 -2
  238. package/dist/particles/incentives/incentives.js.map +1 -1
  239. package/dist/particles/logo/logo.d.ts +3 -2
  240. package/dist/particles/logo/logo.js +20 -8
  241. package/dist/particles/logo/logo.js.map +1 -1
  242. package/dist/particles/navigation-link/navigation-link.d.ts +2 -3
  243. package/dist/particles/navigation-link/navigation-link.js +4 -4
  244. package/dist/particles/navigation-link/navigation-link.js.map +1 -1
  245. package/dist/particles/navigation-link-list/navigation-link-list.d.ts +6 -2
  246. package/dist/particles/navigation-link-list/navigation-link-list.js +18 -7
  247. package/dist/particles/navigation-link-list/navigation-link-list.js.map +1 -1
  248. package/dist/particles/order-details-back/order-details-back.d.ts +28 -0
  249. package/dist/particles/order-details-back/order-details-back.js +52 -0
  250. package/dist/particles/order-details-back/order-details-back.js.map +1 -0
  251. package/dist/particles/order-status-badge/order-status-badge.d.ts +19 -0
  252. package/dist/particles/order-status-badge/order-status-badge.js +50 -0
  253. package/dist/particles/order-status-badge/order-status-badge.js.map +1 -0
  254. package/dist/particles/phone-field/phone-field.js +2 -2
  255. package/dist/particles/phone-field/phone-field.js.map +1 -1
  256. package/dist/particles/select/select.js +6 -6
  257. package/dist/particles/select/select.js.map +1 -1
  258. package/dist/particles/toast/toast.d.ts +1 -1
  259. package/dist/particles/toast/toast.js +32 -6
  260. package/dist/particles/toast/toast.js.map +1 -1
  261. package/dist/schema.graphql +1014 -28
  262. package/dist/sdk/actions/address-action-provider.d.ts +16 -0
  263. package/dist/sdk/actions/address-action-provider.js +20 -0
  264. package/dist/sdk/actions/address-action-provider.js.map +1 -0
  265. package/dist/sdk/actions/analytics-action-provider.js +1 -1
  266. package/dist/sdk/actions/index.d.ts +3 -1
  267. package/dist/sdk/actions/index.js +3 -1
  268. package/dist/sdk/actions/order-action-provider.d.ts +12 -0
  269. package/dist/sdk/actions/order-action-provider.js +16 -0
  270. package/dist/sdk/actions/order-action-provider.js.map +1 -0
  271. package/dist/sdk/actions/types.d.ts +6 -2
  272. package/dist/sdk/address/address-fields.js +38 -0
  273. package/dist/sdk/address/address-fields.js.map +1 -0
  274. package/dist/sdk/address/checkout-readiness.js +17 -0
  275. package/dist/sdk/address/checkout-readiness.js.map +1 -0
  276. package/dist/sdk/address/constants.js +60 -0
  277. package/dist/sdk/address/constants.js.map +1 -0
  278. package/dist/sdk/address/country-display.js +19 -0
  279. package/dist/sdk/address/country-display.js.map +1 -0
  280. package/dist/sdk/address/country-rules.js +30 -0
  281. package/dist/sdk/address/country-rules.js.map +1 -0
  282. package/dist/sdk/address/format-address.js +18 -0
  283. package/dist/sdk/address/format-address.js.map +1 -0
  284. package/dist/sdk/address/iso-countries.js +262 -0
  285. package/dist/sdk/address/iso-countries.js.map +1 -0
  286. package/dist/sdk/address/layouts.js +56 -0
  287. package/dist/sdk/address/layouts.js.map +1 -0
  288. package/dist/sdk/address/messages.js +97 -0
  289. package/dist/sdk/address/messages.js.map +1 -0
  290. package/dist/sdk/address/operations.d.ts +54 -0
  291. package/dist/sdk/address/operations.js +83 -0
  292. package/dist/sdk/address/operations.js.map +1 -0
  293. package/dist/sdk/address/postal-code-lookup-operations.js +23 -0
  294. package/dist/sdk/address/postal-code-lookup-operations.js.map +1 -0
  295. package/dist/sdk/address/postal-code.js +11 -0
  296. package/dist/sdk/address/postal-code.js.map +1 -0
  297. package/dist/sdk/address/schema.js +14 -0
  298. package/dist/sdk/address/schema.js.map +1 -0
  299. package/dist/sdk/address/server-fields.js +25 -0
  300. package/dist/sdk/address/server-fields.js.map +1 -0
  301. package/dist/sdk/address/switch-country.js +57 -0
  302. package/dist/sdk/address/switch-country.js.map +1 -0
  303. package/dist/sdk/address/types.d.ts +26 -0
  304. package/dist/sdk/address/use-address-actions.d.ts +29 -0
  305. package/dist/sdk/address/use-address-actions.js +247 -0
  306. package/dist/sdk/address/use-address-actions.js.map +1 -0
  307. package/dist/sdk/address/use-address-by-postal-code.js +57 -0
  308. package/dist/sdk/address/use-address-by-postal-code.js.map +1 -0
  309. package/dist/sdk/address/validate-address.js +62 -0
  310. package/dist/sdk/address/validate-address.js.map +1 -0
  311. package/dist/sdk/cache/storefront-cache.js +29 -4
  312. package/dist/sdk/cache/storefront-cache.js.map +1 -1
  313. package/dist/sdk/client/fetch-page-data.d.ts +11 -0
  314. package/dist/sdk/client/fetch-page-data.js +12 -1
  315. package/dist/sdk/client/fetch-page-data.js.map +1 -1
  316. package/dist/sdk/content/create-content-runtime.js +1 -8
  317. package/dist/sdk/content/create-content-runtime.js.map +1 -1
  318. package/dist/sdk/data/failed-fields.js +23 -0
  319. package/dist/sdk/data/failed-fields.js.map +1 -0
  320. package/dist/sdk/data/index.js +2 -2
  321. package/dist/sdk/data/page-reactive-data-root.d.ts +45 -7
  322. package/dist/sdk/data/page-reactive-data-root.js +88 -12
  323. package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
  324. package/dist/sdk/data/reactive-list-item-data-provider.js +2 -2
  325. package/dist/sdk/generated/fragment-types.js +2 -0
  326. package/dist/sdk/generated/fragment-types.js.map +1 -1
  327. package/dist/sdk/generated/gql.js +3 -1
  328. package/dist/sdk/generated/gql.js.map +1 -1
  329. package/dist/sdk/generated/graphql.d.ts +8 -0
  330. package/dist/sdk/generated/graphql.js +212 -1
  331. package/dist/sdk/generated/graphql.js.map +1 -1
  332. package/dist/sdk/i18n/messages/en-US.js +138 -1
  333. package/dist/sdk/i18n/messages/en-US.json +138 -1
  334. package/dist/sdk/i18n/messages/en-US.json.d.ts +137 -0
  335. package/dist/sdk/i18n/messages/es-ES.js +138 -1
  336. package/dist/sdk/i18n/messages/es-ES.json +138 -1
  337. package/dist/sdk/i18n/messages/pt-BR.js +138 -1
  338. package/dist/sdk/i18n/messages/pt-BR.json +138 -1
  339. package/dist/sdk/navigation/current-page.js +30 -0
  340. package/dist/sdk/navigation/current-page.js.map +1 -0
  341. package/dist/sdk/navigation/not-found-log.d.ts +28 -0
  342. package/dist/sdk/navigation/not-found-log.js +133 -0
  343. package/dist/sdk/navigation/not-found-log.js.map +1 -0
  344. package/dist/sdk/navigation/path.js +25 -0
  345. package/dist/sdk/navigation/path.js.map +1 -0
  346. package/dist/sdk/navigation/redirect.d.ts +11 -0
  347. package/dist/sdk/navigation/redirect.js +22 -0
  348. package/dist/sdk/navigation/redirect.js.map +1 -0
  349. package/dist/sdk/navigation/report.js +70 -0
  350. package/dist/sdk/navigation/report.js.map +1 -0
  351. package/dist/sdk/navigation/resolve-storefront-href.d.ts +12 -0
  352. package/dist/sdk/navigation/resolve-storefront-href.js +214 -0
  353. package/dist/sdk/navigation/resolve-storefront-href.js.map +1 -0
  354. package/dist/sdk/navigation/route-identity-provider.d.ts +11 -0
  355. package/dist/sdk/navigation/route-identity-provider.js +20 -0
  356. package/dist/sdk/navigation/route-identity-provider.js.map +1 -0
  357. package/dist/sdk/navigation/route-map-provider.d.ts +19 -0
  358. package/dist/sdk/navigation/route-map-provider.js +29 -0
  359. package/dist/sdk/navigation/route-map-provider.js.map +1 -0
  360. package/dist/sdk/navigation/storefront-link.d.ts +16 -0
  361. package/dist/sdk/navigation/storefront-link.js +66 -0
  362. package/dist/sdk/navigation/storefront-link.js.map +1 -0
  363. package/dist/sdk/navigation/types.d.ts +45 -0
  364. package/dist/sdk/navigation/use-storefront-navigation.d.ts +26 -0
  365. package/dist/sdk/navigation/use-storefront-navigation.js +73 -0
  366. package/dist/sdk/navigation/use-storefront-navigation.js.map +1 -0
  367. package/dist/sdk/navigation/use-storefront-route.d.ts +26 -0
  368. package/dist/sdk/navigation/use-storefront-route.js +87 -0
  369. package/dist/sdk/navigation/use-storefront-route.js.map +1 -0
  370. package/dist/sdk/navigation/use-storefront-search-params.d.ts +11 -0
  371. package/dist/sdk/navigation/use-storefront-search-params.js +16 -0
  372. package/dist/sdk/navigation/use-storefront-search-params.js.map +1 -0
  373. package/dist/sdk/orders/completion-badge.d.ts +8 -0
  374. package/dist/sdk/orders/completion-badge.js +47 -0
  375. package/dist/sdk/orders/completion-badge.js.map +1 -0
  376. package/dist/sdk/orders/order-operations.d.ts +19 -0
  377. package/dist/sdk/orders/order-operations.js +21 -0
  378. package/dist/sdk/orders/order-operations.js.map +1 -0
  379. package/dist/sdk/orders/use-order-actions.d.ts +23 -0
  380. package/dist/sdk/orders/use-order-actions.js +62 -0
  381. package/dist/sdk/orders/use-order-actions.js.map +1 -0
  382. package/dist/sdk/payment/gateway-origin.js +26 -13
  383. package/dist/sdk/payment/gateway-origin.js.map +1 -1
  384. package/dist/sdk/payment/gateway.d.ts +14 -2
  385. package/dist/sdk/payment/gateway.js +32 -14
  386. package/dist/sdk/payment/gateway.js.map +1 -1
  387. package/dist/sdk/payment/operations.js +11 -0
  388. package/dist/sdk/payment/operations.js.map +1 -1
  389. package/dist/sdk/payment/submission-registry.d.ts +2 -1
  390. package/dist/sdk/payment/submission-registry.js +12 -8
  391. package/dist/sdk/payment/submission-registry.js.map +1 -1
  392. package/dist/sdk/payment/use-payment-actions.d.ts +5 -3
  393. package/dist/sdk/payment/use-payment-actions.js +5 -2
  394. package/dist/sdk/payment/use-payment-actions.js.map +1 -1
  395. package/dist/sdk/payment/use-place-order.d.ts +6 -3
  396. package/dist/sdk/payment/use-place-order.js +333 -143
  397. package/dist/sdk/payment/use-place-order.js.map +1 -1
  398. package/dist/sdk/seo/page-alternates.d.ts +55 -0
  399. package/dist/sdk/seo/page-alternates.js +132 -0
  400. package/dist/sdk/seo/page-alternates.js.map +1 -0
  401. package/dist/utils/own-value.js +19 -0
  402. package/dist/utils/own-value.js.map +1 -0
  403. package/dist/utils/safe-href.d.ts +4 -4
  404. package/dist/utils/safe-href.js +31 -22
  405. package/dist/utils/safe-href.js.map +1 -1
  406. package/package.json +5 -1
  407. package/skills/storefront-asset/SKILL.md +111 -0
  408. package/skills/storefront-asset/references/api-side.md +153 -0
  409. package/skills/storefront-asset/references/findings.md +40 -0
  410. package/skills/storefront-asset/references/ui-side.md +94 -0
  411. package/src/styles/tailwind.css +27 -0
@@ -1,11 +1,11 @@
1
+ import { PageRef } from "../sdk/navigation/types.js";
1
2
  //#region src/utils/safe-href.d.ts
2
3
  /**
3
- * Builds the sign-in destination, carrying the current path so the shopper can be returned where they
4
+ * The sign-in page, carrying the current logical path so the shopper can be returned where they
4
5
  * started. The sender's half only: nothing stops a hand-crafted link, so the sign-in route is to narrow
5
6
  * the parameter again on arrival. A path the narrowing refuses drops the parameter instead of
6
- * travelling encoded, and so does a router reporting no path — `usePathname()`'s type is why this takes
7
- * a nullable one.
7
+ * travelling encoded, and so does a router reporting no path.
8
8
  */
9
- export declare function resolveSignInHref(pathname: string | null): string;
9
+ export declare function signInTarget(path: string | null): PageRef;
10
10
  //#endregion
11
11
  //# sourceMappingURL=safe-href.d.ts.map
@@ -49,9 +49,8 @@ function resolveSafeHref(raw) {
49
49
  reason: "forbidden-scheme"
50
50
  };
51
51
  }
52
- /** The storefront's own sign-in route, which forwards to the platform's login. A fixed convention,
53
- * never an authored value. */
54
- const SIGN_IN_ROUTE = "/login";
52
+ /** The storefront's own sign-in page, which forwards to the platform's login. Never an authored value. */
53
+ const SIGN_IN_PAGE = { standard: "sign-in" };
55
54
  /** The parameter carrying the shopper's way back, read by the sign-in route and the platform's login. */
56
55
  const RETURN_URL_PARAM = "returnUrl";
57
56
  /**
@@ -61,7 +60,7 @@ const RETURN_URL_PARAM = "returnUrl";
61
60
  * unchanged, so a caller logging the reason still sees which spelling arrived.
62
61
  *
63
62
  * Both ends of the flow narrow through here rather than only the sender, because the sender cannot vouch
64
- * for a URL the shopper typed: the account button builds the destination through `resolveSignInHref`, and
63
+ * for a URL the shopper typed: the account button builds the destination through `signInTarget`, and
65
64
  * the sign-in route revalidates what arrives through `resolveReturnHref`.
66
65
  */
67
66
  function resolveSiteRelativeHref(raw) {
@@ -92,50 +91,60 @@ function resolveNavigableHref(raw) {
92
91
  * `resolveSiteRelativeHref` narrowed further, for a return arriving at the sign-in route: the route
93
92
  * itself is refused. Admitting it sends a shopper who just authenticated back to the trampoline, which
94
93
  * hands them to the platform a second time — a sign-in that never lands where the parameter promised.
94
+ *
95
+ * `signInPath` is the sign-in page's logical path, since a return is one; `null` when the store has no
96
+ * sign-in page, and then no return is the route.
95
97
  */
96
- function resolveReturnHref(raw) {
98
+ function resolveReturnHref(raw, signInPath) {
97
99
  const destination = resolveSiteRelativeHref(raw);
98
100
  if (!destination.safe) return destination;
99
- return isSignInRoute(destination.href) ? {
101
+ return isSignInRoute(destination.href, signInPath) ? {
100
102
  safe: false,
101
103
  reason: "sign-in-route"
102
104
  } : destination;
103
105
  }
104
- /** Whether a site-relative value names the storefront's own sign-in route, in any of its spellings. */
105
- function isSignInRoute(href) {
106
- return pathOf(href) === SIGN_IN_ROUTE;
106
+ /**
107
+ * Whether a site-relative value names the sign-in page at `signInPath`, in any of its spellings. `null`
108
+ * is a store without a sign-in page, where nothing is the route.
109
+ */
110
+ function isSignInRoute(href, signInPath) {
111
+ return signInPath !== null && pathOf(href) === pathOf(signInPath);
107
112
  }
108
113
  /**
109
114
  * `resolveNavigableHref` narrowed for the address a handover targets: the storefront's own sign-in
110
- * route is refused. Sending `/login` to `/login` reloads the same document, which mounts the same
115
+ * route is refused. Sending the route to itself reloads the same document, which mounts the same
111
116
  * handover again — a loop the shopper never gets past, and the mirror of the refusal
112
117
  * `resolveReturnHref` already makes on the way back.
113
118
  *
114
119
  * Only the site-relative spelling is decidable here, being same-origin by construction. An absolute
115
120
  * address needs the document's own origin to compare against, which this module does not have and a
116
121
  * prerender does not either, so `isSignInRoute` is exported for the caller that does. A cross-origin
117
- * login page whose path happens to be `/login` is a different page and is left alone.
122
+ * login page whose path happens to be the route's is a different page and is left alone.
123
+ *
124
+ * `signInPath` is the sign-in page's public path, since the address is a document URL.
118
125
  */
119
- function resolveLoginAddressHref(raw) {
126
+ function resolveLoginAddressHref(raw, signInPath) {
120
127
  const address = resolveNavigableHref(raw);
121
128
  if (!address.safe || !address.href.startsWith("/")) return address;
122
- return isSignInRoute(address.href) ? {
129
+ return isSignInRoute(address.href, signInPath) ? {
123
130
  safe: false,
124
131
  reason: "sign-in-route"
125
132
  } : address;
126
133
  }
127
134
  /**
128
- * Builds the sign-in destination, carrying the current path so the shopper can be returned where they
135
+ * The sign-in page, carrying the current logical path so the shopper can be returned where they
129
136
  * started. The sender's half only: nothing stops a hand-crafted link, so the sign-in route is to narrow
130
137
  * the parameter again on arrival. A path the narrowing refuses drops the parameter instead of
131
- * travelling encoded, and so does a router reporting no path — `usePathname()`'s type is why this takes
132
- * a nullable one.
138
+ * travelling encoded, and so does a router reporting no path.
133
139
  */
134
- function resolveSignInHref(pathname) {
135
- if (!pathname) return SIGN_IN_ROUTE;
136
- const destination = resolveSiteRelativeHref(pathname);
137
- if (!destination.safe) return SIGN_IN_ROUTE;
138
- return `${SIGN_IN_ROUTE}?${RETURN_URL_PARAM}=${encodeURIComponent(destination.href)}`;
140
+ function signInTarget(path) {
141
+ if (!path) return SIGN_IN_PAGE;
142
+ const destination = resolveSiteRelativeHref(path);
143
+ if (!destination.safe) return SIGN_IN_PAGE;
144
+ return {
145
+ standard: "sign-in",
146
+ query: { [RETURN_URL_PARAM]: destination.href }
147
+ };
139
148
  }
140
149
  /** Opaque base for resolving a site-relative value to a path. Reserved by RFC 6761, so it never names a
141
150
  * real host; nothing is navigated to it and only `pathname` is read back. */
@@ -164,6 +173,6 @@ function hasStrippableCharacter(value) {
164
173
  return false;
165
174
  }
166
175
  //#endregion
167
- export { RETURN_URL_PARAM, isSignInRoute, resolveLoginAddressHref, resolveNavigableHref, resolveReturnHref, resolveSafeHref, resolveSignInHref, resolveSiteRelativeHref };
176
+ export { RETURN_URL_PARAM, SIGN_IN_PAGE, isSignInRoute, resolveLoginAddressHref, resolveNavigableHref, resolveReturnHref, resolveSafeHref, resolveSiteRelativeHref, signInTarget };
168
177
 
169
178
  //# sourceMappingURL=safe-href.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"safe-href.js","names":[],"sources":["../../src/utils/safe-href.ts"],"sourcesContent":["export type SafeHrefRejection =\n | 'blank'\n | 'malformed'\n | 'protocol-relative'\n | 'forbidden-scheme'\n | 'absolute'\n | 'sign-in-route';\n\nexport type SafeHrefResult = { safe: true; href: string } | { safe: false; reason: SafeHrefRejection };\n\nconst ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);\n\n/**\n * The only gate on a CMS-authored destination: `http:`, `https:`, `mailto:`, `tel:`, and paths\n * starting with a single `/`. The runtime slice does not enforce a model's `pattern`, and the sukima\n * preflight that does never sees content published after a deploy, so an authored `javascript:` value\n * can reach the component intact and this is what stands between it and an `href`.\n *\n * Rejects rather than sanitizes: a destination that comes back safe is character-for-character the\n * trimmed authored value, so no value is ever silently rewritten into a different working one.\n */\nexport function resolveSafeHref(raw: string): SafeHrefResult {\n const href = raw.trim();\n if (href.length === 0) {\n return { safe: false, reason: 'blank' };\n }\n // Browsers strip ASCII tabs and newlines from a URL before parsing it, so `java<TAB>script:alert(1)`\n // executes. Refusing the whole value is the only defense that does not depend on stripping the same\n // characters in the same order the parser does.\n if (hasStrippableCharacter(href)) {\n return { safe: false, reason: 'malformed' };\n }\n // A URL parser reads `\\` as `/` under a special scheme, so `/\\evil.example` resolves against the\n // store's origin as an authority rather than a path. Any pair of leading slashes is an off-site\n // jump however it is spelled, and only `//` looks like one.\n if (/^[/\\\\]{2}/.test(href)) {\n return { safe: false, reason: 'protocol-relative' };\n }\n if (href.startsWith('/')) {\n return { safe: true, href };\n }\n\n let protocol: string;\n try {\n protocol = new URL(href).protocol;\n } catch {\n return { safe: false, reason: 'malformed' };\n }\n // `new URL` lowercases the protocol, so `JavaScript:` and `javascript:` are the same rejection.\n return ALLOWED_PROTOCOLS.has(protocol) ? { safe: true, href } : { safe: false, reason: 'forbidden-scheme' };\n}\n\n/** The storefront's own sign-in route, which forwards to the platform's login. A fixed convention,\n * never an authored value. */\nconst SIGN_IN_ROUTE = '/login';\n\n/** The parameter carrying the shopper's way back, read by the sign-in route and the platform's login. */\nexport const RETURN_URL_PARAM = 'returnUrl';\n\n/**\n * `resolveSafeHref` narrowed to the site-relative subset: the destinations a shopper may be sent back to\n * across the auth hop. The allowlist admits absolute http(s) values, which are legitimate for an authored\n * link but are exactly what would turn the hop into an open redirect. Every other rejection travels out\n * unchanged, so a caller logging the reason still sees which spelling arrived.\n *\n * Both ends of the flow narrow through here rather than only the sender, because the sender cannot vouch\n * for a URL the shopper typed: the account button builds the destination through `resolveSignInHref`, and\n * the sign-in route revalidates what arrives through `resolveReturnHref`.\n */\nexport function resolveSiteRelativeHref(raw: string): SafeHrefResult {\n const destination = resolveSafeHref(raw);\n if (!destination.safe) {\n return destination;\n }\n return destination.href.startsWith('/') ? destination : { safe: false, reason: 'absolute' };\n}\n\n/** The subset of the allowlist that resolves to a page a document can be handed over to. */\nconst NAVIGABLE_PROTOCOLS = new Set(['http:', 'https:']);\n\n/**\n * `resolveSafeHref` narrowed to the destinations that resolve to a page: site-relative, or absolute\n * `http(s)`. `mailto:` and `tel:` are legitimate for an authored link and refused here, since handing a\n * document over to one leaves the shopper in a mail client instead of a login.\n */\nexport function resolveNavigableHref(raw: string): SafeHrefResult {\n const destination = resolveSafeHref(raw);\n if (!destination.safe || destination.href.startsWith('/')) {\n return destination;\n }\n // The allowlist already parsed this value, so the protocol is readable without a second guard.\n const { protocol } = new URL(destination.href);\n return NAVIGABLE_PROTOCOLS.has(protocol) ? destination : { safe: false, reason: 'forbidden-scheme' };\n}\n\n/**\n * `resolveSiteRelativeHref` narrowed further, for a return arriving at the sign-in route: the route\n * itself is refused. Admitting it sends a shopper who just authenticated back to the trampoline, which\n * hands them to the platform a second time — a sign-in that never lands where the parameter promised.\n */\nexport function resolveReturnHref(raw: string): SafeHrefResult {\n const destination = resolveSiteRelativeHref(raw);\n if (!destination.safe) {\n return destination;\n }\n return isSignInRoute(destination.href) ? { safe: false, reason: 'sign-in-route' } : destination;\n}\n\n/** Whether a site-relative value names the storefront's own sign-in route, in any of its spellings. */\nexport function isSignInRoute(href: string): boolean {\n return pathOf(href) === SIGN_IN_ROUTE;\n}\n\n/**\n * `resolveNavigableHref` narrowed for the address a handover targets: the storefront's own sign-in\n * route is refused. Sending `/login` to `/login` reloads the same document, which mounts the same\n * handover again — a loop the shopper never gets past, and the mirror of the refusal\n * `resolveReturnHref` already makes on the way back.\n *\n * Only the site-relative spelling is decidable here, being same-origin by construction. An absolute\n * address needs the document's own origin to compare against, which this module does not have and a\n * prerender does not either, so `isSignInRoute` is exported for the caller that does. A cross-origin\n * login page whose path happens to be `/login` is a different page and is left alone.\n */\nexport function resolveLoginAddressHref(raw: string): SafeHrefResult {\n const address = resolveNavigableHref(raw);\n if (!address.safe || !address.href.startsWith('/')) {\n return address;\n }\n return isSignInRoute(address.href) ? { safe: false, reason: 'sign-in-route' } : address;\n}\n\n/**\n * Builds the sign-in destination, carrying the current path so the shopper can be returned where they\n * started. The sender's half only: nothing stops a hand-crafted link, so the sign-in route is to narrow\n * the parameter again on arrival. A path the narrowing refuses drops the parameter instead of\n * travelling encoded, and so does a router reporting no path — `usePathname()`'s type is why this takes\n * a nullable one.\n */\nexport function resolveSignInHref(pathname: string | null): string {\n if (!pathname) {\n return SIGN_IN_ROUTE;\n }\n const destination = resolveSiteRelativeHref(pathname);\n if (!destination.safe) {\n return SIGN_IN_ROUTE;\n }\n return `${SIGN_IN_ROUTE}?${RETURN_URL_PARAM}=${encodeURIComponent(destination.href)}`;\n}\n\n/** Opaque base for resolving a site-relative value to a path. Reserved by RFC 6761, so it never names a\n * real host; nothing is navigated to it and only `pathname` is read back. */\nconst PATH_COMPARISON_BASE = 'https://store.invalid';\n\n/**\n * The path of a site-relative value, without its query or fragment and without a trailing slash, so a\n * route comparison reads the route rather than one of its spellings. `/loginhelp` is a different path\n * and stays one.\n *\n * Parsed rather than sliced, because `/account/../login`, `/account\\..\\login` and `/account/%2e%2e/login`\n * are all spellings a URL parser resolves to `/login`. A slice compares them literally and admits them,\n * and the value then reaches a receiver that does resolve them — which puts the shopper back on the\n * trampoline the refusal exists to prevent. Only the comparison is normalized: the caller returns the\n * value as it arrived, so the promise that nothing is silently rewritten still holds.\n */\nfunction pathOf(href: string): string {\n const { pathname } = new URL(href, PATH_COMPARISON_BASE);\n return pathname.replace(/\\/+$/, '') || '/';\n}\n\n/** Any C0 control or space anywhere in the value — the set a URL parser removes or trims. */\nfunction hasStrippableCharacter(value: string): boolean {\n for (const character of value) {\n const code = character.codePointAt(0) ?? 0;\n if (code <= 0x20 || code === 0x7f) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":";AAUA,MAAM,oCAAoB,IAAI,IAAI;CAAC;CAAS;CAAU;CAAW;AAAM,CAAC;;;;;;;;;;AAWxE,SAAgB,gBAAgB,KAA6B;CAC3D,MAAM,OAAO,IAAI,KAAK;CACtB,IAAI,KAAK,WAAW,GAClB,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAQ;CAKxC,IAAI,uBAAuB,IAAI,GAC7B,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAY;CAK5C,IAAI,YAAY,KAAK,IAAI,GACvB,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAoB;CAEpD,IAAI,KAAK,WAAW,GAAG,GACrB,OAAO;EAAE,MAAM;EAAM;CAAK;CAG5B,IAAI;CACJ,IAAI;EACF,WAAW,IAAI,IAAI,IAAI,CAAC,CAAC;CAC3B,QAAQ;EACN,OAAO;GAAE,MAAM;GAAO,QAAQ;EAAY;CAC5C;CAEA,OAAO,kBAAkB,IAAI,QAAQ,IAAI;EAAE,MAAM;EAAM;CAAK,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAmB;AAC5G;;;AAIA,MAAM,gBAAgB;;AAGtB,MAAa,mBAAmB;;;;;;;;;;;AAYhC,SAAgB,wBAAwB,KAA6B;CACnE,MAAM,cAAc,gBAAgB,GAAG;CACvC,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO,YAAY,KAAK,WAAW,GAAG,IAAI,cAAc;EAAE,MAAM;EAAO,QAAQ;CAAW;AAC5F;;AAGA,MAAM,sCAAsB,IAAI,IAAI,CAAC,SAAS,QAAQ,CAAC;;;;;;AAOvD,SAAgB,qBAAqB,KAA6B;CAChE,MAAM,cAAc,gBAAgB,GAAG;CACvC,IAAI,CAAC,YAAY,QAAQ,YAAY,KAAK,WAAW,GAAG,GACtD,OAAO;CAGT,MAAM,EAAE,aAAa,IAAI,IAAI,YAAY,IAAI;CAC7C,OAAO,oBAAoB,IAAI,QAAQ,IAAI,cAAc;EAAE,MAAM;EAAO,QAAQ;CAAmB;AACrG;;;;;;AAOA,SAAgB,kBAAkB,KAA6B;CAC7D,MAAM,cAAc,wBAAwB,GAAG;CAC/C,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO,cAAc,YAAY,IAAI,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAgB,IAAI;AACtF;;AAGA,SAAgB,cAAc,MAAuB;CACnD,OAAO,OAAO,IAAI,MAAM;AAC1B;;;;;;;;;;;;AAaA,SAAgB,wBAAwB,KAA6B;CACnE,MAAM,UAAU,qBAAqB,GAAG;CACxC,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,KAAK,WAAW,GAAG,GAC/C,OAAO;CAET,OAAO,cAAc,QAAQ,IAAI,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAgB,IAAI;AAClF;;;;;;;;AASA,SAAgB,kBAAkB,UAAiC;CACjE,IAAI,CAAC,UACH,OAAO;CAET,MAAM,cAAc,wBAAwB,QAAQ;CACpD,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO,GAAG,cAAc,GAAG,iBAAiB,GAAG,mBAAmB,YAAY,IAAI;AACpF;;;AAIA,MAAM,uBAAuB;;;;;;;;;;;;AAa7B,SAAS,OAAO,MAAsB;CACpC,MAAM,EAAE,aAAa,IAAI,IAAI,MAAM,oBAAoB;CACvD,OAAO,SAAS,QAAQ,QAAQ,EAAE,KAAK;AACzC;;AAGA,SAAS,uBAAuB,OAAwB;CACtD,KAAK,MAAM,aAAa,OAAO;EAC7B,MAAM,OAAO,UAAU,YAAY,CAAC,KAAK;EACzC,IAAI,QAAQ,MAAQ,SAAS,KAC3B,OAAO;CAEX;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"safe-href.js","names":[],"sources":["../../src/utils/safe-href.ts"],"sourcesContent":["import type { PageRef } from '../sdk/navigation/types.js';\n\nexport type SafeHrefRejection =\n | 'blank'\n | 'malformed'\n | 'protocol-relative'\n | 'forbidden-scheme'\n | 'absolute'\n | 'sign-in-route';\n\nexport type SafeHrefResult = { safe: true; href: string } | { safe: false; reason: SafeHrefRejection };\n\nconst ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);\n\n/**\n * The only gate on a CMS-authored destination: `http:`, `https:`, `mailto:`, `tel:`, and paths\n * starting with a single `/`. The runtime slice does not enforce a model's `pattern`, and the sukima\n * preflight that does never sees content published after a deploy, so an authored `javascript:` value\n * can reach the component intact and this is what stands between it and an `href`.\n *\n * Rejects rather than sanitizes: a destination that comes back safe is character-for-character the\n * trimmed authored value, so no value is ever silently rewritten into a different working one.\n */\nexport function resolveSafeHref(raw: string): SafeHrefResult {\n const href = raw.trim();\n if (href.length === 0) {\n return { safe: false, reason: 'blank' };\n }\n // Browsers strip ASCII tabs and newlines from a URL before parsing it, so `java<TAB>script:alert(1)`\n // executes. Refusing the whole value is the only defense that does not depend on stripping the same\n // characters in the same order the parser does.\n if (hasStrippableCharacter(href)) {\n return { safe: false, reason: 'malformed' };\n }\n // A URL parser reads `\\` as `/` under a special scheme, so `/\\evil.example` resolves against the\n // store's origin as an authority rather than a path. Any pair of leading slashes is an off-site\n // jump however it is spelled, and only `//` looks like one.\n if (/^[/\\\\]{2}/.test(href)) {\n return { safe: false, reason: 'protocol-relative' };\n }\n if (href.startsWith('/')) {\n return { safe: true, href };\n }\n\n let protocol: string;\n try {\n protocol = new URL(href).protocol;\n } catch {\n return { safe: false, reason: 'malformed' };\n }\n // `new URL` lowercases the protocol, so `JavaScript:` and `javascript:` are the same rejection.\n return ALLOWED_PROTOCOLS.has(protocol) ? { safe: true, href } : { safe: false, reason: 'forbidden-scheme' };\n}\n\n/** The storefront's own sign-in page, which forwards to the platform's login. Never an authored value. */\nexport const SIGN_IN_PAGE: PageRef = { standard: 'sign-in' };\n\n/** The parameter carrying the shopper's way back, read by the sign-in route and the platform's login. */\nexport const RETURN_URL_PARAM = 'returnUrl';\n\n/**\n * `resolveSafeHref` narrowed to the site-relative subset: the destinations a shopper may be sent back to\n * across the auth hop. The allowlist admits absolute http(s) values, which are legitimate for an authored\n * link but are exactly what would turn the hop into an open redirect. Every other rejection travels out\n * unchanged, so a caller logging the reason still sees which spelling arrived.\n *\n * Both ends of the flow narrow through here rather than only the sender, because the sender cannot vouch\n * for a URL the shopper typed: the account button builds the destination through `signInTarget`, and\n * the sign-in route revalidates what arrives through `resolveReturnHref`.\n */\nexport function resolveSiteRelativeHref(raw: string): SafeHrefResult {\n const destination = resolveSafeHref(raw);\n if (!destination.safe) {\n return destination;\n }\n return destination.href.startsWith('/') ? destination : { safe: false, reason: 'absolute' };\n}\n\n/** The subset of the allowlist that resolves to a page a document can be handed over to. */\nconst NAVIGABLE_PROTOCOLS = new Set(['http:', 'https:']);\n\n/**\n * `resolveSafeHref` narrowed to the destinations that resolve to a page: site-relative, or absolute\n * `http(s)`. `mailto:` and `tel:` are legitimate for an authored link and refused here, since handing a\n * document over to one leaves the shopper in a mail client instead of a login.\n */\nexport function resolveNavigableHref(raw: string): SafeHrefResult {\n const destination = resolveSafeHref(raw);\n if (!destination.safe || destination.href.startsWith('/')) {\n return destination;\n }\n // The allowlist already parsed this value, so the protocol is readable without a second guard.\n const { protocol } = new URL(destination.href);\n return NAVIGABLE_PROTOCOLS.has(protocol) ? destination : { safe: false, reason: 'forbidden-scheme' };\n}\n\n/**\n * `resolveSiteRelativeHref` narrowed further, for a return arriving at the sign-in route: the route\n * itself is refused. Admitting it sends a shopper who just authenticated back to the trampoline, which\n * hands them to the platform a second time — a sign-in that never lands where the parameter promised.\n *\n * `signInPath` is the sign-in page's logical path, since a return is one; `null` when the store has no\n * sign-in page, and then no return is the route.\n */\nexport function resolveReturnHref(raw: string, signInPath: string | null): SafeHrefResult {\n const destination = resolveSiteRelativeHref(raw);\n if (!destination.safe) {\n return destination;\n }\n return isSignInRoute(destination.href, signInPath) ? { safe: false, reason: 'sign-in-route' } : destination;\n}\n\n/**\n * Whether a site-relative value names the sign-in page at `signInPath`, in any of its spellings. `null`\n * is a store without a sign-in page, where nothing is the route.\n */\nexport function isSignInRoute(href: string, signInPath: string | null): boolean {\n return signInPath !== null && pathOf(href) === pathOf(signInPath);\n}\n\n/**\n * `resolveNavigableHref` narrowed for the address a handover targets: the storefront's own sign-in\n * route is refused. Sending the route to itself reloads the same document, which mounts the same\n * handover again — a loop the shopper never gets past, and the mirror of the refusal\n * `resolveReturnHref` already makes on the way back.\n *\n * Only the site-relative spelling is decidable here, being same-origin by construction. An absolute\n * address needs the document's own origin to compare against, which this module does not have and a\n * prerender does not either, so `isSignInRoute` is exported for the caller that does. A cross-origin\n * login page whose path happens to be the route's is a different page and is left alone.\n *\n * `signInPath` is the sign-in page's public path, since the address is a document URL.\n */\nexport function resolveLoginAddressHref(raw: string, signInPath: string | null): SafeHrefResult {\n const address = resolveNavigableHref(raw);\n if (!address.safe || !address.href.startsWith('/')) {\n return address;\n }\n return isSignInRoute(address.href, signInPath) ? { safe: false, reason: 'sign-in-route' } : address;\n}\n\n/**\n * The sign-in page, carrying the current logical path so the shopper can be returned where they\n * started. The sender's half only: nothing stops a hand-crafted link, so the sign-in route is to narrow\n * the parameter again on arrival. A path the narrowing refuses drops the parameter instead of\n * travelling encoded, and so does a router reporting no path.\n */\nexport function signInTarget(path: string | null): PageRef {\n if (!path) {\n return SIGN_IN_PAGE;\n }\n const destination = resolveSiteRelativeHref(path);\n if (!destination.safe) {\n return SIGN_IN_PAGE;\n }\n return { standard: 'sign-in', query: { [RETURN_URL_PARAM]: destination.href } };\n}\n\n/** Opaque base for resolving a site-relative value to a path. Reserved by RFC 6761, so it never names a\n * real host; nothing is navigated to it and only `pathname` is read back. */\nconst PATH_COMPARISON_BASE = 'https://store.invalid';\n\n/**\n * The path of a site-relative value, without its query or fragment and without a trailing slash, so a\n * route comparison reads the route rather than one of its spellings. `/loginhelp` is a different path\n * and stays one.\n *\n * Parsed rather than sliced, because `/account/../login`, `/account\\..\\login` and `/account/%2e%2e/login`\n * are all spellings a URL parser resolves to `/login`. A slice compares them literally and admits them,\n * and the value then reaches a receiver that does resolve them — which puts the shopper back on the\n * trampoline the refusal exists to prevent. Only the comparison is normalized: the caller returns the\n * value as it arrived, so the promise that nothing is silently rewritten still holds.\n */\nfunction pathOf(href: string): string {\n const { pathname } = new URL(href, PATH_COMPARISON_BASE);\n return pathname.replace(/\\/+$/, '') || '/';\n}\n\n/** Any C0 control or space anywhere in the value — the set a URL parser removes or trims. */\nfunction hasStrippableCharacter(value: string): boolean {\n for (const character of value) {\n const code = character.codePointAt(0) ?? 0;\n if (code <= 0x20 || code === 0x7f) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":";AAYA,MAAM,oCAAoB,IAAI,IAAI;CAAC;CAAS;CAAU;CAAW;AAAM,CAAC;;;;;;;;;;AAWxE,SAAgB,gBAAgB,KAA6B;CAC3D,MAAM,OAAO,IAAI,KAAK;CACtB,IAAI,KAAK,WAAW,GAClB,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAQ;CAKxC,IAAI,uBAAuB,IAAI,GAC7B,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAY;CAK5C,IAAI,YAAY,KAAK,IAAI,GACvB,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAoB;CAEpD,IAAI,KAAK,WAAW,GAAG,GACrB,OAAO;EAAE,MAAM;EAAM;CAAK;CAG5B,IAAI;CACJ,IAAI;EACF,WAAW,IAAI,IAAI,IAAI,CAAC,CAAC;CAC3B,QAAQ;EACN,OAAO;GAAE,MAAM;GAAO,QAAQ;EAAY;CAC5C;CAEA,OAAO,kBAAkB,IAAI,QAAQ,IAAI;EAAE,MAAM;EAAM;CAAK,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAmB;AAC5G;;AAGA,MAAa,eAAwB,EAAE,UAAU,UAAU;;AAG3D,MAAa,mBAAmB;;;;;;;;;;;AAYhC,SAAgB,wBAAwB,KAA6B;CACnE,MAAM,cAAc,gBAAgB,GAAG;CACvC,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO,YAAY,KAAK,WAAW,GAAG,IAAI,cAAc;EAAE,MAAM;EAAO,QAAQ;CAAW;AAC5F;;AAGA,MAAM,sCAAsB,IAAI,IAAI,CAAC,SAAS,QAAQ,CAAC;;;;;;AAOvD,SAAgB,qBAAqB,KAA6B;CAChE,MAAM,cAAc,gBAAgB,GAAG;CACvC,IAAI,CAAC,YAAY,QAAQ,YAAY,KAAK,WAAW,GAAG,GACtD,OAAO;CAGT,MAAM,EAAE,aAAa,IAAI,IAAI,YAAY,IAAI;CAC7C,OAAO,oBAAoB,IAAI,QAAQ,IAAI,cAAc;EAAE,MAAM;EAAO,QAAQ;CAAmB;AACrG;;;;;;;;;AAUA,SAAgB,kBAAkB,KAAa,YAA2C;CACxF,MAAM,cAAc,wBAAwB,GAAG;CAC/C,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO,cAAc,YAAY,MAAM,UAAU,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAgB,IAAI;AAClG;;;;;AAMA,SAAgB,cAAc,MAAc,YAAoC;CAC9E,OAAO,eAAe,QAAQ,OAAO,IAAI,MAAM,OAAO,UAAU;AAClE;;;;;;;;;;;;;;AAeA,SAAgB,wBAAwB,KAAa,YAA2C;CAC9F,MAAM,UAAU,qBAAqB,GAAG;CACxC,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,KAAK,WAAW,GAAG,GAC/C,OAAO;CAET,OAAO,cAAc,QAAQ,MAAM,UAAU,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAgB,IAAI;AAC9F;;;;;;;AAQA,SAAgB,aAAa,MAA8B;CACzD,IAAI,CAAC,MACH,OAAO;CAET,MAAM,cAAc,wBAAwB,IAAI;CAChD,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO;EAAE,UAAU;EAAW,OAAO,GAAG,mBAAmB,YAAY,KAAK;CAAE;AAChF;;;AAIA,MAAM,uBAAuB;;;;;;;;;;;;AAa7B,SAAS,OAAO,MAAsB;CACpC,MAAM,EAAE,aAAa,IAAI,IAAI,MAAM,oBAAoB;CACvD,OAAO,SAAS,QAAQ,QAAQ,EAAE,KAAK;AACzC;;AAGA,SAAS,uBAAuB,OAAwB;CACtD,KAAK,MAAM,aAAa,OAAO;EAC7B,MAAM,OAAO,UAAU,YAAY,CAAC,KAAK;EACzC,IAAI,QAAQ,MAAQ,SAAS,KAC3B,OAAO;CAEX;CACA,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "FastStore AI Native component library built on Base UI with AI-editable styles",
5
5
  "license": "MIT",
6
6
  "homepage": "https://developers.vtex.com/docs/guides/faststore",
@@ -50,12 +50,16 @@
50
50
  },
51
51
  "files": [
52
52
  "dist",
53
+ "skills",
53
54
  "src/styles/tailwind.css",
54
55
  "CHANGELOG.md"
55
56
  ],
56
57
  "dependencies": {
57
58
  "@base-ui/react": "^1.3.0",
58
59
  "@graphql-typed-document-node/core": "^3.2.0",
60
+ "@vtex/country-forms": "1.14.0",
61
+ "@vtex/faststore-catalog": "0.0.8",
62
+ "esbuild": "~0.27.3",
59
63
  "graphql": "^16.13.1",
60
64
  "react-hook-form": "^7.85.0",
61
65
  "react-intl": "^10.1.22",
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: storefront-asset
3
+ description: FastStore extension authoring with @vtex/faststore. Scaffold a storefront Asset, write its API side (defineSubgraph) or UI side (defineComponent), and fix storefront-* Findings until vtex extension validate is green. Use when creating or editing an Asset whose asset.yaml says kind storefront.
4
+ ---
5
+
6
+ # FastStore extension
7
+
8
+ A FastStore extension is a **storefront Asset**: one package at `extensions/<ext>/<asset>/` whose
9
+ `asset.yaml` says `kind: storefront`. It holds at most one **API side** (fields it adds to the
10
+ storefront graph) and at most one **UI side** (components a store places). An extension holds at
11
+ most one storefront Asset. The Asset depends on `@vtex/faststore` alone, and `vtex extension` loads
12
+ the checks from that same package, so the code and its checks share one version.
13
+
14
+ Run the CLI from the Account Repo root as `pnpm extension <command>`, the script
15
+ `vtex extension init` writes. With stdout piped it prints JSON and puts its messages on stderr.
16
+ `pnpm extension commands --json` describes every command and flag.
17
+
18
+ The Asset is **green** when `pnpm extension validate <ext>` exits 0. Exit 1 means an error Finding
19
+ and exit 2 means warnings only. An `info` Finding, such as `extension-not-installed`, leaves it
20
+ green.
21
+
22
+ ## Steps
23
+
24
+ 1. **Check the SDK.** Run `pnpm extension validate <ext> --json`. A `provider-invalid` Finding on
25
+ the Asset's `package.json` that names `@vtex/faststore` ("is not a valid storefront provider")
26
+ means the installed SDK predates the storefront provider, and none of the checks below ran.
27
+ `provider-failed` means the provider threw. Either one is the human's to resolve: stop and ask
28
+ which `@vtex/faststore` to install. Done when neither code appears.
29
+ 2. **Create the Asset when it is missing.** On pnpm 11, first set `allowBuilds:` with
30
+ `esbuild: false` in the root's `pnpm-workspace.yaml`, replacing any placeholder value pnpm wrote
31
+ there. Without it pnpm refuses esbuild's build script, and `add` ends in `tool-failed` with the
32
+ Asset's `package.json` but no template. Then `pnpm extension add <ext>/<asset> --kind storefront`
33
+ writes the API side; add `--template ui` for the UI side. When `<ext>` is new too, pass
34
+ `--vendor` and `--description`: outside a terminal `add` exits 3 instead of asking. `add` writes
35
+ `asset.yaml`, `package.json` and the template's files, and installs their packages. An Asset that
36
+ `add` left without its template, or one made by hand, takes its files and `package.json` fields
37
+ from the side's reference. Done when the folder holds every file its sides need.
38
+ 3. **Read the side's reference, then write the change in its shapes.** A resolver, a GraphQL type,
39
+ a secret or data from an outside service is the API side:
40
+ [references/api-side.md](references/api-side.md). A React component, an editable prop or a
41
+ catalog key is the UI side: [references/ui-side.md](references/ui-side.md). A change that needs
42
+ both sides reads both, and the Asset takes the both-sides layout below.
43
+ 4. **Validate until green.** Run `pnpm extension validate <ext> --json`. For each `storefront-*`
44
+ Finding, apply the fix in its row of [references/findings.md](references/findings.md), then run
45
+ it again. Done when it exits 0.
46
+ 5. **Run the Asset's own checks.** In the Asset's folder, `pnpm typecheck`, and for an API side
47
+ `pnpm test`. An Asset made by hand may lack the scripts: run `pnpm exec tsc --noEmit` and
48
+ `pnpm exec vitest run`. Done when each exits 0.
49
+
50
+ ## Layout
51
+
52
+ The provider reads an Asset's sides from `package.json` and two paths. A storefront `asset.yaml`
53
+ holds the envelope and an empty body, so nothing in it changes the layout.
54
+
55
+ | Side | What marks it | Entry |
56
+ | --- | --- | --- |
57
+ | API | `package.json#main`, or, with neither `main` nor `exports["./catalog.json"]`, `src/index.ts` | The declaration module, which `export default`s a `defineSubgraph(...)` |
58
+ | UI | A `src/components/` directory, or `exports["./catalog.json"]` | Every `defineComponent` under `src/components/` |
59
+
60
+ An Asset with both sides names its declaration through `main` (for example `api/index.ts`) and
61
+ keeps `src/index.ts` as the component barrel. Without `main`, a `src/index.ts` beside a
62
+ `./catalog.json` export is the barrel.
63
+
64
+ The templates set these `package.json` fields, and an Asset made by hand sets them itself. Both
65
+ sides take `"type": "module"`, a `typecheck` script and `typescript@^5.9.3` in devDependencies;
66
+ `validate` reports `storefront-typescript-unsupported` without TypeScript 5. The API side adds
67
+ `graphql`, `vitest` and a `test` script. The UI side adds `@types/react`, `react` as a peer, and
68
+ these exports:
69
+
70
+ ```json
71
+ "exports": {
72
+ ".": "./src/index.ts",
73
+ "./catalog.json": "./dist/catalog.json"
74
+ }
75
+ ```
76
+
77
+ A store lists the Asset's package as a dependency and compiles its components from `src/`.
78
+
79
+ ## Environment
80
+
81
+ `dev` needs `VTEX_ACCOUNT` in the repo root's `.env`, or `STOREFRONT_CORE_URL` to override the core
82
+ URL derived from the account. `VTEX_WORKSPACE` defaults to `master`. `VTEX_TOKEN`, which is
83
+ optional, and the value of every secret a declaration lists go in the root's `.env.local`, which
84
+ git ignores. Keep the Asset's folder free of `.env` files, so one place holds every value.
85
+ `validate` reads none of them.
86
+
87
+ ## Running it live
88
+
89
+ `dev` runs until it is stopped, so start it in the background. Start it through the CLI's bin, so a
90
+ stop signal reaches the CLI rather than a pnpm wrapper:
91
+ `node node_modules/@vtex/cli-plugin-extension/dist/bin/vtex-extension.js dev <ext>` from the repo
92
+ root. It serves one local router over every API side and rebuilds each UI side's
93
+ `dist/catalog.json` on every save under `src/`. The first run downloads the Hive Router into
94
+ `.vtex/storefront/`, and the Assets stay `starting` until it lands.
95
+
96
+ `.vtex/dev-session.json` at the repo root lists each Asset's `status` (`starting`, `ready` or
97
+ `failed`), its `findings`, and, once `ready`, its `details`: the router `url`, the `subgraph` and
98
+ the `catalog`. Query the router `url`; the subgraph's port changes on every reload. A Finding
99
+ leaves the Asset `ready`, so read `findings` after each save. The file's shape is provisional.
100
+
101
+ One session runs per repo, and a second `dev` exits 1 and names the running one. SIGTERM or SIGINT
102
+ stops it with exit 0 and removes the file.
103
+
104
+ ## Building it
105
+
106
+ `pnpm extension build <ext>` validates first, then writes each Asset's output to
107
+ `.vtex/build/<ext>/<asset>/` (`--out <dir>` puts it under another root). The API side emits
108
+ `server.mjs` and `extension.graphql`. The UI side emits `catalog.json`, `content-types.json` when
109
+ there is one, and `docs/`. An Asset with an error Finding is `failed` and leaves no folder. The
110
+ output is what the pipeline collects; the UI side's `catalog.json` also lands in the Asset's `dist/`,
111
+ where its export points, so a store build finds it. `--json` prints each Asset's status.
@@ -0,0 +1,153 @@
1
+ # The API side
2
+
3
+ The API side is one declaration module: `src/index.ts`, or the file `package.json#main` names when
4
+ the Asset has a UI side too.
5
+
6
+ ## The declaration
7
+
8
+ ```ts
9
+ import { defineSubgraph, gql } from '@vtex/faststore/graphql';
10
+
11
+ const REVIEWS_BY_SLUG: Record<string, Array<{ id: string; rating: number; text: string }>> = {
12
+ 'sample-product-slug': [{ id: '1', rating: 5, text: 'Replace this with data from your own source.' }],
13
+ };
14
+
15
+ export default defineSubgraph({
16
+ vendor: 'acme',
17
+
18
+ typeDefs: gql`
19
+ extend type Product @key(fields: "slug") {
20
+ slug: String! @external
21
+ reviews: [Review!]!
22
+ }
23
+
24
+ type Review {
25
+ id: ID!
26
+ rating: Int!
27
+ text: String!
28
+ }
29
+ `,
30
+
31
+ extends: ['Product'],
32
+
33
+ resolvers: {
34
+ Product: {
35
+ reviews: (product: { slug: string }) => REVIEWS_BY_SLUG[product.slug] ?? [],
36
+ },
37
+ },
38
+ });
39
+ ```
40
+
41
+ `@vtex/faststore/graphql` exports `defineSubgraph`, `gql` and the types `SubgraphContext`,
42
+ `SubgraphDefinition` and `SubgraphResolvers`. `defineSubgraph` returns its argument unchanged; the
43
+ provider builds the schema from it.
44
+
45
+ | Field | What it is |
46
+ | --- | --- |
47
+ | `vendor` | Required. Matches `^[a-z][a-z0-9]*$`: lowercase letters and digits, starting with a letter. It prefixes every name the Asset contributes. |
48
+ | `typeDefs` | SDL in a `gql` tag, with bare names. Write `@key` and `@external` on the entity you extend; the provider injects the federation preamble. |
49
+ | `extends` | The core entities the SDL extends. `Product`, keyed by `slug`, is the only one. |
50
+ | `resolvers` | Keyed by bare names. The provider renames the keys to the prefixed names and adds each entity's `__resolveReference`. |
51
+ | `secrets` | Optional. The environment names the resolvers read; see Secrets. |
52
+ | `name` | Optional. The subgraph name; the package name by default. |
53
+
54
+ ## The vendor prefix
55
+
56
+ The build prefixes every name the Asset contributes with `<vendor>_`: fields added to `Product`,
57
+ root `Query` and `Mutation` fields, and each declared type with every reference to it. The
58
+ declaration above exposes `Product.acme_reviews` of type `[acme_Review!]!`. Write bare names in the
59
+ declaration and let the build add the prefix. The `@key` field of an extended entity keeps its core
60
+ name.
61
+
62
+ ## The resolver context
63
+
64
+ A resolver's third argument has exactly these five keys:
65
+
66
+ | Key | Value |
67
+ | --- | --- |
68
+ | `ctx.account` | The `x-vtex-account` header, `''` when absent |
69
+ | `ctx.workspace` | The `x-vtex-workspace` header, `''` when absent |
70
+ | `ctx.headers` | `x-vtex-locale`, `x-vtex-segment` and `x-vtex-session`, each only when the request carried it |
71
+ | `ctx.token` | The raw `authorization` header, `''` when absent |
72
+ | `ctx.secrets` | The declared secret names that are set, mapped to their values |
73
+
74
+ ```ts
75
+ import type { SubgraphContext } from '@vtex/faststore/graphql';
76
+
77
+ const reviews = (product: { slug: string }, _args: unknown, ctx: SubgraphContext) =>
78
+ fetchReviews(product.slug, { apiKey: ctx.secrets.ACME_API_KEY, account: ctx.account });
79
+ ```
80
+
81
+ ## Secrets
82
+
83
+ `secrets: ['ACME_API_KEY']` declares the environment names the Asset reads. Each name matches
84
+ `^[A-Z][A-Z0-9_]*$`. The provider rejects `PATH`, `HOME`, `PWD`, `SHELL`, `TMPDIR` and any name
85
+ that starts with `VTEX_` or `NODE_`. A declared name that is not set is absent from `ctx.secrets`;
86
+ `dev` logs one warning for each. Values come from the repo root's `.env` and `.env.local`; keep
87
+ secret values in `.env.local`, which git ignores. A resolver reads them through `ctx.secrets` only.
88
+
89
+ ## The capability boundary
90
+
91
+ The provider lints the declaration and every `./` or `../` module it imports. Each rule is an error
92
+ Finding at the line it flags:
93
+
94
+ | Rule | What it flags | What to write instead |
95
+ | --- | --- | --- |
96
+ | `process-env` | Any `process.env` member access | Declare the name in `secrets` and read `ctx.secrets` |
97
+ | `fs-import` | A runtime import of `fs` or `fs/promises`, bare or `node:` | Fetch the data from a service |
98
+ | `child-process-import` | A runtime import of `child_process` | Call a service over HTTP |
99
+
100
+ Static `import`, `import x = require()`, `require()`, dynamic `import()` and value re-exports all
101
+ count; type-only imports do not. The lint does not walk packages, so route every secret through `ctx.secrets` even when a
102
+ dependency could read the environment: the deployed server scrubs the declared names from its
103
+ environment after boot.
104
+
105
+ ## The test
106
+
107
+ `src/index.test.ts` sits beside the declaration. It builds the subgraph with `buildSubgraph` from
108
+ `@vtex/faststore/cli` and resolves the field through `_entities`, the join a federated query makes:
109
+
110
+ ```ts
111
+ import { buildSubgraph } from '@vtex/faststore/cli';
112
+ import { graphql } from 'graphql';
113
+ import { describe, expect, it } from 'vitest';
114
+ import definition from './index';
115
+
116
+ describe('acme extension', () => {
117
+ it('builds a subgraph and resolves the sample field via _entities', async () => {
118
+ const schema = buildSubgraph(definition);
119
+
120
+ const result = await graphql({
121
+ schema,
122
+ source: `
123
+ query ($representations: [_Any!]!) {
124
+ _entities(representations: $representations) {
125
+ ... on Product {
126
+ acme_reviews {
127
+ rating
128
+ text
129
+ }
130
+ }
131
+ }
132
+ }
133
+ `,
134
+ variableValues: {
135
+ representations: [{ __typename: 'Product', slug: 'sample-product-slug' }],
136
+ },
137
+ });
138
+
139
+ expect(result.errors).toBeUndefined();
140
+ const entities = result.data?._entities as Array<{ acme_reviews: Array<{ rating: number }> }>;
141
+ expect(entities[0]?.acme_reviews[0]?.rating).toBe(5);
142
+ });
143
+ });
144
+ ```
145
+
146
+ The test sees the built schema, so it queries the prefixed name (`acme_reviews`). `buildSubgraph`
147
+ and `_entities` are the whole surface a test uses.
148
+
149
+ ## What validate checks here
150
+
151
+ `validate` runs the capability lint, loads the declaration and builds the subgraph, which is where
152
+ the `vendor` grammar, the `extends` list, the secret names and the SDL fail. It then composes the
153
+ slice with the storefront core schema. `dev` runs the same checks at boot and on every save.
@@ -0,0 +1,40 @@
1
+ # The Findings
2
+
3
+ Every code `vtex extension validate` reports for a storefront Asset. All are errors. `file` is
4
+ relative to the repo root, and a Finding with a `range` points at its line and column. `dev` reports
5
+ the API-side and UI-side codes for the same faults and replaces them on every reload, and never the
6
+ two kind rules. `build` validates each Asset first, so it reports every code `validate` does before
7
+ it writes, plus `storefront-build-failed`.
8
+
9
+ ## The Asset's code
10
+
11
+ | Code | Trigger | On | Fix |
12
+ | --- | --- | --- | --- |
13
+ | `storefront-typescript-unsupported` | The Asset's `typescript` is missing or has no TypeScript 5 compiler API, so the capability lint did not run | `package.json` | `pnpm add -D typescript@^5.9.3` in the Asset's folder |
14
+ | `storefront-capability-process-env` | A `process.env` member access in the declaration or a module it imports | The source line | Declare the name in `secrets` and read `ctx.secrets` |
15
+ | `storefront-capability-fs-import` | A runtime import of `fs` or `fs/promises` | The source line | Fetch the data from a service; a resolver has no filesystem |
16
+ | `storefront-capability-child-process-import` | A runtime import of `child_process` | The source line | Call a service; a resolver spawns no process |
17
+ | `storefront-declaration-invalid` | `main` names no file; the declaration does not bundle or import; it does not `export default defineSubgraph(...)`; or the build rejects it: a `vendor` outside `^[a-z][a-z0-9]*$`, an `extends` entry other than `Product`, a `@key` other than `slug`, a secret name outside the grammar, or SDL that does not parse | The declaration entry | The message names the field or the SDL error. On a UI-only Asset's `src/index.ts`, add `exports["./catalog.json"]`: without it the barrel was read as a declaration |
18
+ | `storefront-composition-failed` | The built slice does not compose with the storefront core schema: a type or field that collides with core's, or a federation directive the composer rejects. In `dev`, also a conflict between two Assets' slices | The declaration entry | Change the SDL as the message says. Keep `@key(fields: "slug")` on `Product` and bare names elsewhere |
19
+ | `storefront-catalog-export-missing` | The Asset has `src/components/` and `package.json#exports` has no `./catalog.json` key | `package.json` | Add `"./catalog.json": "./dist/catalog.json"` and `".": "./src/index.ts"` to `exports` |
20
+ | `storefront-catalog-export-invalid` | The `./catalog.json` export names no file, or a path that is not `./`-relative, has `..` or resolves outside the Asset through a link; `dev` and `build` write where it points, so the catalog goes to `dist/` meanwhile | `package.json` | Point the export at a file inside the Asset: `"./catalog.json": "./dist/catalog.json"` |
21
+ | `storefront-catalog-invalid` | The catalog build threw: no component under `src/components/` (`NoExtensionComponents`), two components with one name, a fragment spread with no definition (`UnresolvedSpread`), no `package.json#name` (`MissingPackageName`), or a top-level `defineActions` | `package.json` | The message names the file or component. Add a `defineComponent`, rename the duplicate, drop the spread, set `name`, or remove `defineActions` |
22
+ | `storefront-build-failed` | The build step after `validate` passed failed: no or invalid `tsconfig.json`, a TypeScript diagnostic, the `server.mjs` bundle, two component docs sharing a basename, or the catalog | `package.json` | The message is the runner's error. Fix what it names and run `pnpm extension build` again |
23
+
24
+ `storefront-declaration-invalid`, `storefront-catalog-invalid` and `storefront-build-failed` carry
25
+ the build's own error verbatim in their message: read it before changing code.
26
+
27
+ ## The kind rules
28
+
29
+ `validate` and `build` check these before either side; `dev` does not.
30
+
31
+ | Code | Trigger | On | Fix |
32
+ | --- | --- | --- | --- |
33
+ | `storefront-one-per-extension` | The extension already has a storefront Asset, and this one is its second | The Asset's `asset.yaml`, at `kind` | Merge the two into one Asset with both sides, or move this one to its own extension |
34
+ | `storefront-store-extension-not-installed` | A store under `stores/` depends on the Asset's package, and an account that uses that store does not list the extension under `installed:` in `accounts.yaml` | The store's `package.json` | Add `<vendor>.<ext>@<range>` to each such account's `installed:`, or drop the dependency |
35
+
36
+ ## The provider's own faults
37
+
38
+ `provider-invalid` on the Asset's `package.json` means the installed `@vtex/faststore` has no
39
+ storefront provider. `provider-failed` means the provider threw. Both belong to the SDK, so the
40
+ human decides which `@vtex/faststore` to install.
@@ -0,0 +1,94 @@
1
+ # The UI side
2
+
3
+ The UI side is the set of components under `src/components/`, each registered with
4
+ `defineComponent` from `@vtex/faststore`, and the barrel `src/index.ts` a store imports them from.
5
+ The provider builds a catalog of the registrations. A store reads the catalog to place the
6
+ components and compiles them from `src/`; the Asset ships no JavaScript build.
7
+
8
+ ## A component
9
+
10
+ ```tsx
11
+ import { defineComponent } from '@vtex/faststore';
12
+ import type { ComponentPropsWithRef } from 'react';
13
+
14
+ /** Props for {@link HeroBanner}. Extends all native `<section>` attributes. */
15
+ export type HeroBannerProps = ComponentPropsWithRef<'section'> & {
16
+ /** The headline text. */
17
+ title?: string;
18
+ };
19
+
20
+ export const HeroBannerDefinition = defineComponent<HeroBannerProps>({
21
+ name: 'HeroBanner',
22
+ importPath: '@acme/reviews-ui',
23
+ component: HeroBanner,
24
+ editor: { label: 'Hero Banner', category: 'content' },
25
+ schema: {
26
+ title: { type: 'string', label: 'Title' },
27
+ },
28
+ });
29
+
30
+ export function HeroBanner({ title, className, ...props }: HeroBannerProps) {
31
+ return (
32
+ <section {...props} className={className} data-fs-hero-banner="">
33
+ <h1>{title}</h1>
34
+ </section>
35
+ );
36
+ }
37
+ ```
38
+
39
+ | Field | What it is |
40
+ | --- | --- |
41
+ | `name` | The exported function's name. |
42
+ | `importPath` | The Asset's `package.json#name`. A store imports the component from there. |
43
+ | `component` | The function. A function declaration hoists, so the definition may come first. |
44
+ | `editor` | `label`, the name an editor shows, and `category`, a free string that groups it. `description` and `icon` are optional. |
45
+ | `schema` | The editable props. Each has a `type` (`string`, `number`, `boolean`, `object`, `array` or a list of string literals), an optional `label` and an optional `default`; an `array` may name its allowed elements in `items`. Values are serializable: strings, numbers, booleans, objects and arrays of them. |
46
+
47
+ The provider finds definitions by a static scan of `src/components/`, recursive, skipping
48
+ `*.test.tsx`, `*.stories.tsx` and `generated/`. Export each one as a top-level `const` named
49
+ `<Component>Definition` whose argument is an object literal.
50
+
51
+ The root element carries `data-fs-<component>=""` as its identity, and `data-fs-<component>-<prop>`
52
+ for each prop that changes its look. The component accepts `className` and renders it. A store
53
+ styles it through those attributes, so the component ships no CSS.
54
+
55
+ ## The barrel
56
+
57
+ `src/index.ts` re-exports every component and its definition:
58
+
59
+ ```ts
60
+ export type { HeroBannerProps } from './components/HeroBanner';
61
+ export { HeroBanner, HeroBannerDefinition } from './components/HeroBanner';
62
+ ```
63
+
64
+ It is the `exports["."]` target. With `exports["./catalog.json"]` set, the provider reads it as the
65
+ barrel, never as a declaration.
66
+
67
+ ## The catalog
68
+
69
+ Each registration becomes one catalog entry keyed `<packageName>:<Component>`, for example
70
+ `@acme/reviews-ui:HeroBanner`. The package name, taken verbatim from `package.json#name`, is the
71
+ namespace, so two Assets never collide.
72
+
73
+ `dev` builds `dist/catalog.json` at boot and on every save under `src/`; `exports["./catalog.json"]`
74
+ points there, and git ignores `dist/`. `build` writes the same file there beside its output folder,
75
+ so a store built after `pnpm extension build` finds it without a `dev` loop. `validate` builds the
76
+ catalog into the Asset's `.vtex/storefront/` scratch folder and leaves `dist/` as it is. A failed
77
+ build keeps the previous `catalog.json` until the next save fixes it.
78
+
79
+ A store sees the Asset as a UI side through the `./catalog.json` export alone. Without it, no store
80
+ sees the components, and `validate` reports `storefront-catalog-export-missing`. The export must
81
+ name a `./` path inside the Asset, since `dev` and `build` write the catalog where it points; one
82
+ that does not is `storefront-catalog-export-invalid`, and the catalog goes to `dist/` meanwhile.
83
+
84
+ ## Props and content only
85
+
86
+ Keep each component to its props and editor content. A GraphQL fragment spread fails the catalog
87
+ build with `UnresolvedSpread` (reported as `storefront-catalog-invalid`), because no convention yet
88
+ names where a storefront Asset's generated fragments live.
89
+
90
+ ## What validate checks here
91
+
92
+ `validate` fails a UI side whose `package.json` lacks the `./catalog.json` export, whose
93
+ `src/components/` declares no component, that has two components with one name, or that spreads a
94
+ fragment with no definition.
@@ -63,4 +63,31 @@
63
63
  [data-fs-card-entry-fieldset] {
64
64
  min-inline-size: 0;
65
65
  }
66
+
67
+ /* Baseline look and viewport bound for the select popup. Nothing else styles it, and a matter-composed
68
+ popup is portaled out of reach of a store's descendant selectors, so unstyled it is transparent text
69
+ drawn over the next field, content-wide and free to run past the viewport. `--anchor-width` and
70
+ `--available-height` are set on the positioner by Base UI; the latter bounds the popup (not the list)
71
+ to the viewport. Preflight's `border-box` counts the 1px border inside that bound, and the flex column
72
+ hands the leftover height, minus any composed scroll arrows, to the list, so a tall popup never
73
+ overruns the viewport by the border or the arrows. `@layer base` so any merchant utility class (`max-h-60`,
74
+ `overflow-*`) outranks it under Tailwind 4's layer order. */
75
+ [data-fs-select-popup] {
76
+ max-height: var(--available-height);
77
+ display: flex;
78
+ flex-direction: column;
79
+ min-width: var(--anchor-width);
80
+ background-color: var(--color-bg);
81
+ border: 1px solid var(--color-border);
82
+ border-radius: var(--radius-md);
83
+ }
84
+
85
+ /* The list, not the popup, is the scroll container: that is the Select contract (`SelectList` is the
86
+ "scrollable container"), and Base UI's scroll arrows watch the list's scroll position, so a popup
87
+ that scrolled instead would leave them inert. `min-height: 0` lets the flex item shrink below its
88
+ content so the popup's bound reaches it. */
89
+ [data-fs-select-list] {
90
+ min-height: 0;
91
+ overflow-y: auto;
92
+ }
66
93
  }