@tokenoftrust/storefront-runner 1.3.2

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 (310) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +42 -0
  3. package/apps/storefront/astro.config.mjs +66 -0
  4. package/apps/storefront/dev-plugins/dev-loop-state.mjs +147 -0
  5. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +238 -0
  6. package/apps/storefront/env.d.ts +162 -0
  7. package/apps/storefront/migrations/0000_baseline.sql +54 -0
  8. package/apps/storefront/migrations/0001_products_fts.sql +9 -0
  9. package/apps/storefront/migrations/README.md +54 -0
  10. package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
  11. package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
  12. package/apps/storefront/migrations/meta/_journal.json +20 -0
  13. package/apps/storefront/package.json +42 -0
  14. package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
  15. package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
  16. package/apps/storefront/public/brand/northwind-og.svg +13 -0
  17. package/apps/storefront/public/favicon.svg +5 -0
  18. package/apps/storefront/public/js/dashboard-team.js +147 -0
  19. package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
  20. package/apps/storefront/public/shared/commerce-chrome.css +369 -0
  21. package/apps/storefront/src/components/Badge.astro +34 -0
  22. package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
  23. package/apps/storefront/src/components/CollectionCard.astro +56 -0
  24. package/apps/storefront/src/components/EmptyState.astro +55 -0
  25. package/apps/storefront/src/components/Img.astro +68 -0
  26. package/apps/storefront/src/components/ProductCard.astro +228 -0
  27. package/apps/storefront/src/components/Section.astro +39 -0
  28. package/apps/storefront/src/components/Seo.astro +70 -0
  29. package/apps/storefront/src/components/WidgetFrame.astro +34 -0
  30. package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +531 -0
  31. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +597 -0
  32. package/apps/storefront/src/components/admin/AdminFoxyCartSettingsPanel.astro +227 -0
  33. package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +465 -0
  34. package/apps/storefront/src/components/admin/AdminProductsTab.astro +901 -0
  35. package/apps/storefront/src/components/admin/AdminReportingTab.astro +435 -0
  36. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +782 -0
  37. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +1223 -0
  38. package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
  39. package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
  40. package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
  41. package/apps/storefront/src/components/chrome/NavDropdown.astro +201 -0
  42. package/apps/storefront/src/components/chrome/SiteFooter.astro +201 -0
  43. package/apps/storefront/src/components/chrome/SiteHeader.astro +234 -0
  44. package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
  45. package/apps/storefront/src/components/commerce/ArticleCard.astro +242 -0
  46. package/apps/storefront/src/components/commerce/BrandGrid.astro +175 -0
  47. package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
  48. package/apps/storefront/src/components/commerce/FoxySidecartStyles.astro +61 -0
  49. package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
  50. package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
  51. package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
  52. package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
  53. package/apps/storefront/src/components/commerce/ReviewList.astro +54 -0
  54. package/apps/storefront/src/components/commerce/ReviewSummary.astro +44 -0
  55. package/apps/storefront/src/components/commerce/TagFilterCloud.astro +149 -0
  56. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +37 -0
  57. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +99 -0
  58. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +45 -0
  59. package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
  60. package/apps/storefront/src/components/compliance/PactActNotice.astro +35 -0
  61. package/apps/storefront/src/components/compliance/Prop65Warning.astro +80 -0
  62. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +38 -0
  63. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +35 -0
  64. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +53 -0
  65. package/apps/storefront/src/components/home/Hero.astro +164 -0
  66. package/apps/storefront/src/components/islands/AgeGate.tsx +109 -0
  67. package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
  68. package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
  69. package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
  70. package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
  71. package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
  72. package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
  73. package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
  74. package/apps/storefront/src/components/islands/VariantSelector.tsx +375 -0
  75. package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
  76. package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
  77. package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
  78. package/apps/storefront/src/components/marketing/Faq.astro +37 -0
  79. package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
  80. package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
  81. package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
  82. package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
  83. package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
  84. package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
  85. package/apps/storefront/src/components/marketing/Steps.astro +36 -0
  86. package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
  87. package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
  88. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +56 -0
  89. package/apps/storefront/src/components/membership/TierCards.astro +164 -0
  90. package/apps/storefront/src/components/membership/TierComparisonTable.astro +148 -0
  91. package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
  92. package/apps/storefront/src/components/plp/FacetSidebar.astro +199 -0
  93. package/apps/storefront/src/components/plp/Pagination.astro +99 -0
  94. package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
  95. package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +95 -0
  96. package/apps/storefront/src/components/subscription/HowItWorksSteps.astro +67 -0
  97. package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +67 -0
  98. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +101 -0
  99. package/apps/storefront/src/config/adminTargetTenant.ts +28 -0
  100. package/apps/storefront/src/config/devTenantSeed.ts +78 -0
  101. package/apps/storefront/src/config/devTenants.ts +16 -0
  102. package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
  103. package/apps/storefront/src/config/resolver.ts +102 -0
  104. package/apps/storefront/src/config/tenants.ts +87 -0
  105. package/apps/storefront/src/layouts/Layout.astro +368 -0
  106. package/apps/storefront/src/lib/admin/README.md +122 -0
  107. package/apps/storefront/src/lib/admin/ai/approvalMode.ts +21 -0
  108. package/apps/storefront/src/lib/admin/ai/askEnvelope.ts +48 -0
  109. package/apps/storefront/src/lib/admin/ai/audit.ts +14 -0
  110. package/apps/storefront/src/lib/admin/ai/index.ts +6 -0
  111. package/apps/storefront/src/lib/admin/ai/operationalRuns.ts +155 -0
  112. package/apps/storefront/src/lib/admin/ai/recommendation.ts +20 -0
  113. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +172 -0
  114. package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +65 -0
  115. package/apps/storefront/src/lib/admin/envelope.ts +10 -0
  116. package/apps/storefront/src/lib/admin/evidence/evidenceReference.ts +32 -0
  117. package/apps/storefront/src/lib/admin/memoizeByEnvelope.ts +27 -0
  118. package/apps/storefront/src/lib/admin/orderClient.ts +146 -0
  119. package/apps/storefront/src/lib/admin/seed.ts +54 -0
  120. package/apps/storefront/src/lib/admin/services/mock/aiWorkflows.ts +59 -0
  121. package/apps/storefront/src/lib/admin/services/mock/customers.ts +86 -0
  122. package/apps/storefront/src/lib/admin/services/mock/evidence.ts +99 -0
  123. package/apps/storefront/src/lib/admin/services/mock/foxyCartSettings.ts +78 -0
  124. package/apps/storefront/src/lib/admin/services/mock/fulfillment.ts +62 -0
  125. package/apps/storefront/src/lib/admin/services/mock/index.ts +12 -0
  126. package/apps/storefront/src/lib/admin/services/mock/orders.ts +184 -0
  127. package/apps/storefront/src/lib/admin/services/mock/products.ts +105 -0
  128. package/apps/storefront/src/lib/admin/services/mock/reporting.ts +51 -0
  129. package/apps/storefront/src/lib/admin/services/mock/settings.ts +70 -0
  130. package/apps/storefront/src/lib/admin/services/mock/tenants.ts +29 -0
  131. package/apps/storefront/src/lib/admin/services/orders.ts +300 -0
  132. package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +67 -0
  133. package/apps/storefront/src/lib/admin/viewmodels/customers.ts +56 -0
  134. package/apps/storefront/src/lib/admin/viewmodels/fulfillment.ts +49 -0
  135. package/apps/storefront/src/lib/admin/viewmodels/orders.ts +92 -0
  136. package/apps/storefront/src/lib/admin/viewmodels/products.ts +74 -0
  137. package/apps/storefront/src/lib/admin/viewmodels/reporting.ts +34 -0
  138. package/apps/storefront/src/lib/admin/viewmodels/settings.ts +64 -0
  139. package/apps/storefront/src/lib/adminTenantEnvelope.ts +90 -0
  140. package/apps/storefront/src/lib/analytics/index.ts +47 -0
  141. package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
  142. package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
  143. package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
  144. package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
  145. package/apps/storefront/src/lib/auth/loginGate.ts +119 -0
  146. package/apps/storefront/src/lib/auth/route.ts +146 -0
  147. package/apps/storefront/src/lib/auth/session.ts +203 -0
  148. package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
  149. package/apps/storefront/src/lib/basePath.ts +41 -0
  150. package/apps/storefront/src/lib/cfImage.ts +55 -0
  151. package/apps/storefront/src/lib/chrome/model.ts +244 -0
  152. package/apps/storefront/src/lib/chrome/parseConfig.ts +34 -0
  153. package/apps/storefront/src/lib/colors.ts +97 -0
  154. package/apps/storefront/src/lib/commerce/foxyCartSettings.ts +253 -0
  155. package/apps/storefront/src/lib/content-edit/applyDiff.ts +91 -0
  156. package/apps/storefront/src/lib/content-edit/client.ts +230 -0
  157. package/apps/storefront/src/lib/content-edit/index.ts +14 -0
  158. package/apps/storefront/src/lib/content-edit/route.ts +25 -0
  159. package/apps/storefront/src/lib/content-edit/types.ts +19 -0
  160. package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
  161. package/apps/storefront/src/lib/d1/catalog.ts +366 -0
  162. package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
  163. package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
  164. package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
  165. package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
  166. package/apps/storefront/src/lib/demoGate.ts +113 -0
  167. package/apps/storefront/src/lib/dev/activityIngestToken.ts +78 -0
  168. package/apps/storefront/src/lib/dev/activityStore.ts +72 -0
  169. package/apps/storefront/src/lib/dev/ai-widget-client.js +308 -0
  170. package/apps/storefront/src/lib/dev/apiBase.ts +14 -0
  171. package/apps/storefront/src/lib/dev/cliSignInCode.ts +155 -0
  172. package/apps/storefront/src/lib/dev/devDraft.ts +133 -0
  173. package/apps/storefront/src/lib/dev/hostedCockpit.ts +169 -0
  174. package/apps/storefront/src/lib/dev/livePreview.ts +77 -0
  175. package/apps/storefront/src/lib/dev/runtimeStore.ts +120 -0
  176. package/apps/storefront/src/lib/dev/storeStatus.ts +142 -0
  177. package/apps/storefront/src/lib/diag/redact.ts +20 -0
  178. package/apps/storefront/src/lib/edgeCache.ts +128 -0
  179. package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
  180. package/apps/storefront/src/lib/env.ts +60 -0
  181. package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
  182. package/apps/storefront/src/lib/format.ts +38 -0
  183. package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
  184. package/apps/storefront/src/lib/imageAttrs.ts +55 -0
  185. package/apps/storefront/src/lib/jsonld.ts +166 -0
  186. package/apps/storefront/src/lib/maintenance.ts +71 -0
  187. package/apps/storefront/src/lib/membership/model.ts +97 -0
  188. package/apps/storefront/src/lib/memoryKv.ts +32 -0
  189. package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
  190. package/apps/storefront/src/lib/placeholder.ts +99 -0
  191. package/apps/storefront/src/lib/previewReload.ts +125 -0
  192. package/apps/storefront/src/lib/priceBuckets.ts +33 -0
  193. package/apps/storefront/src/lib/rawChrome.ts +505 -0
  194. package/apps/storefront/src/lib/rawMarketingHtml.ts +136 -0
  195. package/apps/storefront/src/lib/search/index.ts +74 -0
  196. package/apps/storefront/src/lib/sections.ts +129 -0
  197. package/apps/storefront/src/lib/securityHeaders.ts +31 -0
  198. package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
  199. package/apps/storefront/src/lib/storyblok/provider.ts +607 -0
  200. package/apps/storefront/src/lib/subscription/model.ts +68 -0
  201. package/apps/storefront/src/lib/tenantRouting.ts +91 -0
  202. package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
  203. package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
  204. package/apps/storefront/src/lib/tot/query.ts +249 -0
  205. package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
  206. package/apps/storefront/src/lib/url.ts +142 -0
  207. package/apps/storefront/src/lib/wishlist.ts +63 -0
  208. package/apps/storefront/src/middleware/index.ts +455 -0
  209. package/apps/storefront/src/pages/404.astro +61 -0
  210. package/apps/storefront/src/pages/[...slug].astro +90 -0
  211. package/apps/storefront/src/pages/admin.astro +1675 -0
  212. package/apps/storefront/src/pages/api/admin/orders/[id].ts +53 -0
  213. package/apps/storefront/src/pages/api/admin/orders.ts +71 -0
  214. package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
  215. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
  216. package/apps/storefront/src/pages/api/auth/send.ts +85 -0
  217. package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
  218. package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
  219. package/apps/storefront/src/pages/api/checkout-style.css.ts +43 -0
  220. package/apps/storefront/src/pages/api/cli/policy.ts +70 -0
  221. package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +164 -0
  222. package/apps/storefront/src/pages/api/csp-report.ts +25 -0
  223. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
  224. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
  225. package/apps/storefront/src/pages/api/dev/activity.ts +124 -0
  226. package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +73 -0
  227. package/apps/storefront/src/pages/api/dev/diff.ts +68 -0
  228. package/apps/storefront/src/pages/api/dev/file.ts +57 -0
  229. package/apps/storefront/src/pages/api/dev/locate.ts +125 -0
  230. package/apps/storefront/src/pages/api/dev/publish.ts +128 -0
  231. package/apps/storefront/src/pages/api/dev/tree.ts +70 -0
  232. package/apps/storefront/src/pages/api/dev/write.ts +116 -0
  233. package/apps/storefront/src/pages/api/newsletter.ts +104 -0
  234. package/apps/storefront/src/pages/api/request-access.ts +172 -0
  235. package/apps/storefront/src/pages/auth/login.astro +149 -0
  236. package/apps/storefront/src/pages/auth/magic.astro +105 -0
  237. package/apps/storefront/src/pages/capabilities.astro +292 -0
  238. package/apps/storefront/src/pages/collections/[handle].astro +177 -0
  239. package/apps/storefront/src/pages/collections/index.astro +59 -0
  240. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
  241. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
  242. package/apps/storefront/src/pages/dashboard/index.astro +160 -0
  243. package/apps/storefront/src/pages/index.astro +290 -0
  244. package/apps/storefront/src/pages/llms.txt.ts +92 -0
  245. package/apps/storefront/src/pages/products/[handle].astro +402 -0
  246. package/apps/storefront/src/pages/robots.txt.ts +24 -0
  247. package/apps/storefront/src/pages/saved.astro +35 -0
  248. package/apps/storefront/src/pages/search.astro +150 -0
  249. package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
  250. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +1280 -0
  251. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +229 -0
  252. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +144 -0
  253. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +116 -0
  254. package/apps/storefront/src/pages/style-guide/index.astro +98 -0
  255. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
  256. package/apps/storefront/src/styles/global.css +292 -0
  257. package/apps/storefront/src/themes/reference.ts +68 -0
  258. package/apps/storefront/src/themes/schema.ts +111 -0
  259. package/apps/storefront/src/themes/tenants/index.ts +51 -0
  260. package/apps/storefront/tsconfig.json +16 -0
  261. package/apps/storefront/vitest.config.ts +22 -0
  262. package/package.json +26 -0
  263. package/packages/public-runtime/LICENSE +58 -0
  264. package/packages/public-runtime/package.json +22 -0
  265. package/packages/public-runtime/src/binary-path.ts +68 -0
  266. package/packages/public-runtime/src/capabilities.ts +89 -0
  267. package/packages/public-runtime/src/catalog-api.ts +81 -0
  268. package/packages/public-runtime/src/catalog-d1.ts +192 -0
  269. package/packages/public-runtime/src/checkout-style-publish.ts +200 -0
  270. package/packages/public-runtime/src/checkout-style.ts +375 -0
  271. package/packages/public-runtime/src/csp.ts +109 -0
  272. package/packages/public-runtime/src/customization-preview.ts +197 -0
  273. package/packages/public-runtime/src/customization-reconcile.ts +63 -0
  274. package/packages/public-runtime/src/customization-runtime.ts +157 -0
  275. package/packages/public-runtime/src/customization-scripts.ts +64 -0
  276. package/packages/public-runtime/src/customization-versioning.ts +95 -0
  277. package/packages/public-runtime/src/foxy-orders.ts +136 -0
  278. package/packages/public-runtime/src/foxy.ts +196 -0
  279. package/packages/public-runtime/src/hot-standby-readiness.ts +140 -0
  280. package/packages/public-runtime/src/index.ts +34 -0
  281. package/packages/public-runtime/src/product.ts +159 -0
  282. package/packages/public-runtime/src/search.ts +45 -0
  283. package/packages/public-runtime/src/tenant-assets.ts +444 -0
  284. package/packages/public-runtime/src/tenant.ts +384 -0
  285. package/packages/public-runtime/tsconfig.json +8 -0
  286. package/packages/public-runtime/vitest.config.ts +8 -0
  287. package/pnpm-workspace.yaml +7 -0
  288. package/scripts/build/copy-tenant-assets.mjs +69 -0
  289. package/scripts/dev/ai-edit.mjs +183 -0
  290. package/scripts/dev/checkout-watch.mjs +116 -0
  291. package/scripts/dev/file-browser.mjs +159 -0
  292. package/scripts/dev/file-writer.mjs +141 -0
  293. package/scripts/dev/git-status.mjs +171 -0
  294. package/scripts/dev/locate-handler.mjs +163 -0
  295. package/scripts/dev/locate.mjs +112 -0
  296. package/scripts/dev/port-check.mjs +48 -0
  297. package/scripts/dev/safe-path.mjs +109 -0
  298. package/scripts/dev/shot.mjs +46 -0
  299. package/scripts/dev/tenant-source-root.mjs +76 -0
  300. package/scripts/dev/unified-diff.mjs +81 -0
  301. package/scripts/tot-dev.mjs +376 -0
  302. package/tenants/README.md +14 -0
  303. package/tenants/home/content/chrome.json +7 -0
  304. package/tenants/home/content/home.html +58 -0
  305. package/tenants/home/content/home.json +11 -0
  306. package/tenants/home/public/js/storefront.js +210 -0
  307. package/tenants/home/public/logo-wordmark.png +0 -0
  308. package/tenants/home/public/pages/storefront.css +212 -0
  309. package/tenants/home/theme.json +6 -0
  310. package/tsconfig.base.json +20 -0
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Order READ DTO — the FROZEN v1 contract shape the storefront consumes from
3
+ * tot-foxycart's `GET /commerce/orders[/:orderId]` (card 8585 / U1). Kept in
4
+ * `@tot/public-runtime` (types only, no secrets, edge-safe) so the server-side
5
+ * order client (U2), the Orders admin route (U2), and the view-model mapping (U3)
6
+ * all code against one source of truth.
7
+ *
8
+ * Authoritative spec: `docs/storefront-foxycart-contract.md` §9 (tot-foxycart).
9
+ *
10
+ * READ-ONLY (scope-locks): no write/mutation shapes here. Two fields are
11
+ * ENRICHED-later by U4 (the tot20 identity/age join) and are `null`/`[]` until
12
+ * then — `verification` (summary) and `verificationDetail` (detail). Consumers
13
+ * MUST tolerate that (render risk "pending", readiness/evidence empty) so no
14
+ * contract change is needed when U4 lands.
15
+ */
16
+
17
+ /** Payment state of an order. */
18
+ export type FoxyOrderPayment = "authorized" | "captured" | "refunded";
19
+
20
+ /** Fulfillment / queue state of an order. */
21
+ export type FoxyOrderFulfillment =
22
+ | "unfulfilled"
23
+ | "ready_to_ship"
24
+ | "hold_review_required"
25
+ | "completed";
26
+
27
+ export interface FoxyOrderCustomer {
28
+ /** ToT-core customer id, or null for a guest / unlinked order. */
29
+ id: string | null;
30
+ name: string;
31
+ }
32
+
33
+ /** A row in the orders list. All fields populated by U1 EXCEPT `verification`. */
34
+ export interface FoxyOrderSummary {
35
+ /** Order / transaction id (string). */
36
+ id: string;
37
+ /** ISO 8601 placed-at timestamp. */
38
+ placedAt: string;
39
+ currency: string;
40
+ customer: FoxyOrderCustomer;
41
+ /** Human one-liner, e.g. "Vape Pen Starter Kit +1 more". */
42
+ itemsSummary: string;
43
+ itemCount: number;
44
+ /** Order total in MAJOR currency units (e.g. dollars). */
45
+ total: number;
46
+ payment: FoxyOrderPayment;
47
+ fulfillment: FoxyOrderFulfillment;
48
+ /** Why the order is held, or null. */
49
+ blocker: string | null;
50
+ /**
51
+ * Identity/age verification summary — ENRICHED by U4 (tot20 join). `null` from
52
+ * U1; consumers render risk as "pending" until it is filled.
53
+ */
54
+ verification: FoxyOrderVerification | null;
55
+ }
56
+
57
+ /** U4-filled verification summary (shape reserved; opaque to U1/U2/U3). */
58
+ export interface FoxyOrderVerification {
59
+ riskLevel?: string;
60
+ [k: string]: unknown;
61
+ }
62
+
63
+ export interface FoxyOrderLineItem {
64
+ sku: string;
65
+ name: string;
66
+ quantity: number;
67
+ /** Unit price in MAJOR currency units. */
68
+ unitPrice: number;
69
+ }
70
+
71
+ export interface FoxyOrderTotals {
72
+ subtotal: number;
73
+ shipping: number;
74
+ estimatedTax: number;
75
+ taxesAndFees: number;
76
+ total: number;
77
+ }
78
+
79
+ export interface FoxyOrderShippingAddress {
80
+ line1: string;
81
+ line2: string;
82
+ city: string;
83
+ region: string;
84
+ postalCode: string;
85
+ country: string;
86
+ name: string;
87
+ }
88
+
89
+ /** U4-filled per-order verification detail; `readinessChecks`/`evidenceTimeline` empty from U1. */
90
+ export interface FoxyOrderVerificationDetail {
91
+ readinessChecks: unknown[];
92
+ evidenceTimeline: unknown[];
93
+ }
94
+
95
+ /** A single order's full detail (summary + line items, totals, address, verification detail). */
96
+ export interface FoxyOrderDetail extends FoxyOrderSummary {
97
+ /** The excise line (TOT_EXCISE_TAX) is NOT here — it surfaces in totals.taxesAndFees. */
98
+ lineItems: FoxyOrderLineItem[];
99
+ totals: FoxyOrderTotals;
100
+ shippingAddress: FoxyOrderShippingAddress | null;
101
+ verificationDetail: FoxyOrderVerificationDetail;
102
+ }
103
+
104
+ export interface FoxyOrderPage {
105
+ limit: number;
106
+ cursor: string | null;
107
+ /** Opaque cursor for the next page, or null on the last page. */
108
+ nextCursor: string | null;
109
+ /** Total matching orders when the source can count, else null. */
110
+ total: number | null;
111
+ }
112
+
113
+ /** `GET /commerce/orders` response envelope. */
114
+ export interface FoxyOrderListResponse {
115
+ orders: FoxyOrderSummary[];
116
+ page: FoxyOrderPage;
117
+ }
118
+
119
+ /** `GET /commerce/orders/:orderId` response envelope. */
120
+ export interface FoxyOrderDetailResponse {
121
+ order: FoxyOrderDetail;
122
+ }
123
+
124
+ /** Optional list filters (all server-side; see contract §9). */
125
+ export interface FoxyOrderListFilters {
126
+ queue?: string;
127
+ status?: string;
128
+ /** ISO datetime lower bound. */
129
+ from?: string;
130
+ /** ISO datetime upper bound. */
131
+ to?: string;
132
+ /** 1–100; server defaults to 25. */
133
+ limit?: number;
134
+ /** Opaque cursor from a prior page's `nextCursor`. */
135
+ cursor?: string;
136
+ }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * FoxyCart cart engine — renderer-safe helpers (Track A).
3
+ *
4
+ * The storefront's whole job as a FoxyCart front end is small: include Foxy's
5
+ * loader.js, and emit add-to-cart links/forms whose product name/value pairs are
6
+ * HMAC-signed with the store's "cart validation" secret so the price can't be
7
+ * tampered. This module builds the fields for a catalog variant and (in signed
8
+ * mode) signs them with WebCrypto HMAC-SHA256 — pure, dependency-free, and safe
9
+ * in both `astro dev` (Node 20) and the Cloudflare Worker runtime.
10
+ *
11
+ * The excise-tax line is NOT computed here — it materializes inside the FoxyCart
12
+ * cart via the store's pre-cart webhook + `tot_excise_tax` coupon (Track B, the
13
+ * tot-foxycart service). See docs / the excise-tax-checkout recipe.
14
+ *
15
+ * Signing reference (Foxy "HMAC product verification"):
16
+ * hash = HMAC-SHA256( productcode + fieldname + fieldvalue , storeSecret )
17
+ * link/form field name becomes `fieldname||hash` (value unchanged).
18
+ * Editable fields (quantity) hash the value `--OPEN--` and emit
19
+ * `fieldname||hash||open`. Cart-level params (cart, coupon, output, …) and
20
+ * `h:`/`x:`/`__`/`utm_`-prefixed params are never signed.
21
+ */
22
+ import type { CatalogProduct, CatalogVariant } from "./product.js";
23
+
24
+ /**
25
+ * Per-tenant FoxyCart wiring. Public config only — the signing SECRET is never
26
+ * stored here (it's an env-injected Worker binding, resolved per request). In
27
+ * `unsigned-demo` mode there is no secret at all: links go to a demo store that
28
+ * has cart validation turned OFF, so a developer can run the full cart locally
29
+ * (and on our shared preview worker) with zero secrets.
30
+ */
31
+ export interface FoxyCommerceConfig {
32
+ /** Foxy store subdomain, e.g. "tot-demo" → tot-demo.foxycart.com. */
33
+ storeSubDomain: string;
34
+ /**
35
+ * Full cart/checkout host. Defaults to `${storeSubDomain}.foxycart.com`; set
36
+ * when the store uses a branded checkout domain (e.g. secure.merchant.com).
37
+ */
38
+ storeDomain?: string;
39
+ /**
40
+ * "signed" (preview/live: cart validation ON, HMAC every field) or
41
+ * "unsigned-demo" (local/preview demo: validation OFF, no secret needed).
42
+ */
43
+ mode: "signed" | "unsigned-demo";
44
+ /**
45
+ * Optional per-tenant CSS to brand Foxy's sidecart. Foxy's v2.0 sidecart
46
+ * renders into our own page DOM (root `#fc`, light DOM — no iframe/shadow), so
47
+ * it inherits the theme's CSS custom properties and can be restyled with plain
48
+ * CSS. `<FoxySidecartStyles>` already emits a brand baseline derived from the
49
+ * theme tokens; this string is APPENDED after it for bespoke overrides. Scope
50
+ * every rule under `#fc` and use `!important` to beat Foxy's own
51
+ * responsive_styles.css (it loads after our <head> style). NB: this styles the
52
+ * in-page sidecart only — the hosted checkout page is a separate origin,
53
+ * themed in the Foxy admin template.
54
+ */
55
+ sidecartCss?: string;
56
+ }
57
+
58
+ /** Resolved, request-scoped commerce facts stashed on `Astro.locals`. */
59
+ export interface ResolvedFoxyCommerce extends FoxyCommerceConfig {
60
+ /** `https://<storeDomain>` — the loader + cart origin (for CSP + links). */
61
+ origin: string;
62
+ /** `https://<storeDomain>/cart` — the add-to-cart form action / cart link. */
63
+ cartUrl: string;
64
+ /** `https://cdn.foxycart.com/<storeSubDomain>/loader.js`. */
65
+ loaderSrc: string;
66
+ }
67
+
68
+ /** A single cart field before signing. `open` marks a user-editable field. */
69
+ export interface CartField {
70
+ name: string;
71
+ value: string;
72
+ open?: boolean;
73
+ }
74
+
75
+ /** A cart field ready to render (name may carry the `||hash[||open]` suffix). */
76
+ export interface RenderedCartField {
77
+ name: string;
78
+ value: string;
79
+ }
80
+
81
+ /**
82
+ * The add-to-cart payload for one variant, split so an island can render a form:
83
+ * `hidden` are fixed inputs; `quantityName` is the name to put on the visible
84
+ * quantity stepper (signed-and-`||open` in signed mode, plain "quantity" else).
85
+ */
86
+ export interface VariantCartPayload {
87
+ hidden: RenderedCartField[];
88
+ quantityName: string;
89
+ }
90
+
91
+ const FOXY_HOST_SUFFIX = ".foxycart.com";
92
+
93
+ /** Resolve the full request-scoped commerce facts from a tenant's config. */
94
+ export function resolveFoxyCommerce(
95
+ config: FoxyCommerceConfig,
96
+ ): ResolvedFoxyCommerce {
97
+ const storeDomain =
98
+ config.storeDomain ?? `${config.storeSubDomain}${FOXY_HOST_SUFFIX}`;
99
+ const origin = `https://${storeDomain}`;
100
+ return {
101
+ ...config,
102
+ storeDomain,
103
+ origin,
104
+ cartUrl: `${origin}/cart`,
105
+ loaderSrc: `https://cdn.foxycart.com/${config.storeSubDomain}/loader.js`,
106
+ };
107
+ }
108
+
109
+ /**
110
+ * The raw (unsigned) cart fields for a variant: name, price, code, image, the
111
+ * variant's options, and an editable quantity. `code` is the Foxy "productcode"
112
+ * — the salt every other field is hashed against — so it must be stable per SKU.
113
+ */
114
+ export function cartFieldsForVariant(
115
+ product: CatalogProduct,
116
+ variant: CatalogVariant,
117
+ ): { code: string; fields: CartField[] } {
118
+ const code = variant.sku && variant.sku.length ? variant.sku : variant.id;
119
+ const image = product.images.find((i) => i.id === variant.image_id)?.url ??
120
+ product.images[0]?.url;
121
+ const fields: CartField[] = [
122
+ { name: "name", value: variant.title || product.title },
123
+ { name: "price", value: variant.price.toFixed(2) },
124
+ { name: "code", value: code },
125
+ { name: "quantity", value: "1", open: true },
126
+ ];
127
+ if (image) fields.push({ name: "image", value: image });
128
+ // Variant options (Flavor, Nicotine strength, …) travel as product options so
129
+ // the cart line reflects the exact variant the shopper picked.
130
+ for (const [optName, optVal] of Object.entries(variant.option_values)) {
131
+ if (optName && optVal) fields.push({ name: optName, value: optVal });
132
+ }
133
+ return { code, fields };
134
+ }
135
+
136
+ /** Lowercase hex HMAC-SHA256(message) under `secret`, via WebCrypto. */
137
+ async function hmacHex(secret: string, message: string): Promise<string> {
138
+ const enc = new TextEncoder();
139
+ const key = await crypto.subtle.importKey(
140
+ "raw",
141
+ enc.encode(secret),
142
+ { name: "HMAC", hash: "SHA-256" },
143
+ false,
144
+ ["sign"],
145
+ );
146
+ const sig = await crypto.subtle.sign("HMAC", key, enc.encode(message));
147
+ return [...new Uint8Array(sig)]
148
+ .map((b) => b.toString(16).padStart(2, "0"))
149
+ .join("");
150
+ }
151
+
152
+ /**
153
+ * Sign one field for product `code`. Editable fields hash the sentinel
154
+ * `--OPEN--` and gain the `||open` suffix so Foxy accepts a user-entered value.
155
+ */
156
+ async function signField(
157
+ secret: string,
158
+ code: string,
159
+ field: CartField,
160
+ ): Promise<RenderedCartField> {
161
+ const valueToHash = field.open ? "--OPEN--" : field.value;
162
+ const hash = await hmacHex(secret, code + field.name + valueToHash);
163
+ const name = field.open
164
+ ? `${field.name}||${hash}||open`
165
+ : `${field.name}||${hash}`;
166
+ return { name, value: field.value };
167
+ }
168
+
169
+ /**
170
+ * Build the render-ready add-to-cart payload for a variant. In `unsigned-demo`
171
+ * mode fields pass through verbatim (no secret touched); in `signed` mode every
172
+ * field is HMAC-signed. Deterministic output → the signed form is edge-cacheable.
173
+ */
174
+ export async function buildVariantCartPayload(
175
+ product: CatalogProduct,
176
+ variant: CatalogVariant,
177
+ opts: { mode: FoxyCommerceConfig["mode"]; secret?: string },
178
+ ): Promise<VariantCartPayload> {
179
+ const { code, fields } = cartFieldsForVariant(product, variant);
180
+ const signed = opts.mode === "signed";
181
+ if (signed && !opts.secret) {
182
+ throw new Error(
183
+ "buildVariantCartPayload: signed mode requires a cart secret",
184
+ );
185
+ }
186
+
187
+ const rendered: RenderedCartField[] = signed
188
+ ? await Promise.all(fields.map((f) => signField(opts.secret!, code, f)))
189
+ : fields.map((f) => ({ name: f.name, value: f.value }));
190
+
191
+ // Split the quantity field out: the island puts it on the visible stepper.
192
+ const qtyIdx = fields.findIndex((f) => f.name === "quantity");
193
+ const quantityName = rendered[qtyIdx]?.name ?? "quantity";
194
+ const hidden = rendered.filter((_, i) => i !== qtyIdx);
195
+ return { hidden, quantityName };
196
+ }
@@ -0,0 +1,140 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Hot Standby readiness contract (hsby-A2, epic #8600).
3
+ //
4
+ // The machine-readable shape every Hot Standby surface shares: the sync report
5
+ // (hsby-B3) and preview/evidence/grants/commerce producers each write their own
6
+ // section; the merchant/staff dashboard (hsby-F1) and the Go scorecard feed
7
+ // (hsby-F3/H3) render and export it. Contract semantics, hard rules (never
8
+ // fabricate; unknown ≠ a guessed value; every blocker has a DRI), and the
9
+ // versioning policy: docs/hot-standby-readiness-contract.md.
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+
12
+ /** Worst-first order matters: see overallReadinessState(). */
13
+ export type ReadinessState =
14
+ | "unknown"
15
+ | "blocked"
16
+ | "pending"
17
+ | "in_progress"
18
+ | "ready";
19
+
20
+ export type ReadinessArea =
21
+ | "catalog_sync"
22
+ | "content_theme"
23
+ | "preview"
24
+ | "compliance_evidence"
25
+ | "developer_access"
26
+ | "commerce_cutover";
27
+
28
+ export interface ReadinessBlocker {
29
+ /** Stable slug, e.g. "sample-store-missing-shopify-token". */
30
+ id: string;
31
+ area: ReadinessArea;
32
+ summary: string;
33
+ /** Named owner (person or role). A blocker without a DRI is invalid. */
34
+ dri: string;
35
+ /** ISO date the blocker was recorded. */
36
+ since: string;
37
+ }
38
+
39
+ export interface CatalogSyncStatus {
40
+ state: ReadinessState;
41
+ /** ISO timestamp of the last successful sync; null if never synced. */
42
+ lastSuccessAt: string | null;
43
+ sourcePlatform: "shopify" | "custom" | "other";
44
+ /** Opaque incremental-sync marker (source cursor); null before first sync. */
45
+ sourceMarker: string | null;
46
+ /** EA starts on_demand; tightens as automation lands (offer doc §Freshness). */
47
+ mode: "on_demand" | "scheduled" | "realtime";
48
+ /** Merchant-safe pointer to the hsby-B3 sync report; null if none. */
49
+ reportRef: string | null;
50
+ blockingError: string | null;
51
+ }
52
+
53
+ export interface ContentThemeStatus {
54
+ state: ReadinessState;
55
+ themeId: string | null;
56
+ pagesPorted: number;
57
+ pagesTotal: number;
58
+ /** One-liner from the migration manifest's fidelity audit; null if unaudited. */
59
+ fidelityNote: string | null;
60
+ }
61
+
62
+ export interface PreviewStatus {
63
+ state: ReadinessState;
64
+ previewUrl: string | null;
65
+ lastReconcileAt: string | null;
66
+ /** False = last reconcile failed; last-good preview stays live, error shows here. */
67
+ lastReconcileOk: boolean | null;
68
+ error: string | null;
69
+ }
70
+
71
+ export interface ComplianceEvidenceStatus {
72
+ state: ReadinessState;
73
+ /** Real evidence-gate output only — never synthesized. */
74
+ gatePassing: boolean | null;
75
+ lastEvidenceAt: string | null;
76
+ reportRef: string | null;
77
+ }
78
+
79
+ export interface DeveloperAccessStatus {
80
+ state: ReadinessState;
81
+ invitedCount: number;
82
+ activeCount: number;
83
+ lastActivityAt: string | null;
84
+ /** Failed app-binding / entitlement states, merchant-safe strings (hsby-E4). */
85
+ entitlementFailures: string[];
86
+ }
87
+
88
+ export interface CommerceCutoverStatus {
89
+ state: ReadinessState;
90
+ checkoutMode: "none" | "unsigned_demo" | "signed";
91
+ /** hsby-G2 live-readiness gate items; empty until a live pilot is approved. */
92
+ gateChecklist: Array<{ item: string; done: boolean }>;
93
+ /** Only ever true after an explicit human go recorded on the board. */
94
+ cutoverApproved: boolean;
95
+ }
96
+
97
+ export interface HotStandbyReadiness {
98
+ /** Bump rules: docs/hot-standby-readiness-contract.md §Versioning. */
99
+ contractVersion: 1;
100
+ tenantId: string;
101
+ appDomain: string;
102
+ /** ISO timestamp of assembly — renderers display age, never hide staleness. */
103
+ generatedAt: string;
104
+ catalogSync: CatalogSyncStatus;
105
+ contentTheme: ContentThemeStatus;
106
+ preview: PreviewStatus;
107
+ complianceEvidence: ComplianceEvidenceStatus;
108
+ developerAccess: DeveloperAccessStatus;
109
+ commerceCutover: CommerceCutoverStatus;
110
+ blockers: ReadinessBlocker[];
111
+ }
112
+
113
+ const WORST_FIRST: readonly ReadinessState[] = [
114
+ "blocked",
115
+ "unknown",
116
+ "pending",
117
+ "in_progress",
118
+ "ready",
119
+ ];
120
+
121
+ /**
122
+ * Worst-of-sections rollup. Renderers use this, not their own derivation, so
123
+ * staff and merchant views can never disagree about the headline state. A
124
+ * non-empty blockers list forces "blocked" regardless of section states.
125
+ */
126
+ export function overallReadinessState(r: HotStandbyReadiness): ReadinessState {
127
+ if (r.blockers.length > 0) return "blocked";
128
+ const states = [
129
+ r.catalogSync.state,
130
+ r.contentTheme.state,
131
+ r.preview.state,
132
+ r.complianceEvidence.state,
133
+ r.developerAccess.state,
134
+ r.commerceCutover.state,
135
+ ];
136
+ for (const worst of WORST_FIRST) {
137
+ if (states.includes(worst)) return worst;
138
+ }
139
+ return "ready";
140
+ }
@@ -0,0 +1,34 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // @tot/public-runtime — the PUBLIC, edge-shipped runtime model.
3
+ //
4
+ // Open-sourceable, renderer-safe surface. It ships into the Cloudflare Worker
5
+ // (edge) bundle and is the public IP boundary, so it MUST stay node-free and MUST
6
+ // NOT import @tot/private-controlplane. Machine-enforced by
7
+ // scripts/playbooks/check-public-runtime-purity.sh (wired into `pnpm check`).
8
+ //
9
+ // Purpose + the public/private split, in full:
10
+ // docs/architecture/decisions/0008-public-private-runtime-split.md
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+
13
+ // The storefront renderer model: tenant + product + catalog types, the CSP
14
+ // builder, the customization read/apply runtime, and the read-only pointer /
15
+ // preview / script types the renderer needs. Read/apply only.
16
+ export * from "./product.js";
17
+ export * from "./tenant.js";
18
+ export * from "./foxy.js";
19
+ export * from "./foxy-orders.js";
20
+ export * from "./capabilities.js";
21
+ export * from "./checkout-style.js";
22
+ export * from "./checkout-style-publish.js";
23
+ export * from "./catalog-api.js";
24
+ export * from "./search.js";
25
+ export * from "./catalog-d1.js";
26
+ export * from "./csp.js";
27
+ export * from "./customization-preview.js";
28
+ export * from "./customization-runtime.js";
29
+ export * from "./customization-versioning.js";
30
+ export * from "./customization-scripts.js";
31
+ export * from "./customization-reconcile.js";
32
+ export * from "./tenant-assets.js";
33
+ export * from "./binary-path.js";
34
+ export * from "./hot-standby-readiness.js";
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Canonical storefront catalog model.
3
+ *
4
+ * This is the SINGLE SOURCE OF TRUTH for the product shape that flows:
5
+ * Shopify --(ETL transform)--> these types --(load)--> ToT catalog API
6
+ * ToT catalog API --(ToTClient)--> these types --> Astro storefront
7
+ *
8
+ * Every record is tenant-scoped. Handles/slugs are unique PER TENANT.
9
+ *
10
+ * NOTE on grain: a Shopify "product" maps to one CatalogProduct with embedded
11
+ * variants/images/options. This is deliberately NOT the per-SKU grain of ToT's
12
+ * excise `excise_tax_product_by_app_domain` table — see DECISIONS.md
13
+ * ("ToT catalog store").
14
+ */
15
+
16
+ export type ProductStatus = "active" | "draft" | "archived";
17
+
18
+ export type Currency = string; // ISO 4217, e.g. "USD"
19
+
20
+ export interface SourceRef {
21
+ /** Origin platform. Phase 1 is always "shopify". */
22
+ platform: "shopify";
23
+ /** Stable source id, e.g. Shopify legacyResourceId or gid. Upsert key. */
24
+ source_id: string;
25
+ /** Original handle/slug from the source platform (pre-collision-resolution). */
26
+ source_handle?: string;
27
+ }
28
+
29
+ export interface SeoFields {
30
+ title?: string;
31
+ description?: string;
32
+ }
33
+
34
+ export interface PriceRange {
35
+ min: number;
36
+ max: number;
37
+ currency: Currency;
38
+ }
39
+
40
+ export interface ProductOption {
41
+ name: string;
42
+ position: number;
43
+ values: string[];
44
+ }
45
+
46
+ export interface CatalogImage {
47
+ id: string;
48
+ /** Final URL after rehost (R2/Cloudflare Images) OR original Shopify CDN URL. */
49
+ url: string;
50
+ alt?: string;
51
+ position: number;
52
+ /** Intrinsic dimensions — required for responsive images + CLS-free layout. */
53
+ width?: number;
54
+ height?: number;
55
+ /** Variant ids (CatalogVariant.id) this image is associated with. */
56
+ variant_ids?: string[];
57
+ /** True if rehosted to our own storage; false if still pointing at Shopify CDN. */
58
+ rehosted?: boolean;
59
+ }
60
+
61
+ export interface Metafield {
62
+ namespace: string;
63
+ key: string;
64
+ value: string;
65
+ type?: string;
66
+ }
67
+
68
+ export interface InventoryState {
69
+ quantity?: number;
70
+ /** Shopify inventory policy: "deny" | "continue". */
71
+ policy?: string;
72
+ available: boolean;
73
+ }
74
+
75
+ export interface CatalogVariant {
76
+ tenant_id: string;
77
+ id: string; // stable internal id (tenant-unique)
78
+ product_id: string;
79
+ source: SourceRef;
80
+ sku?: string;
81
+ title: string;
82
+ price: number;
83
+ compare_at_price?: number;
84
+ currency: Currency;
85
+ /** Map of optionName -> chosen value, e.g. { "Color": "Black", "Size": "M" }. */
86
+ option_values: Record<string, string>;
87
+ inventory: InventoryState;
88
+ barcode?: string;
89
+ weight?: number;
90
+ weight_unit?: string;
91
+ position: number;
92
+ /** CatalogImage.id of the variant's featured image, if any. */
93
+ image_id?: string;
94
+ }
95
+
96
+ export interface CatalogProduct {
97
+ tenant_id: string;
98
+ /** Stable internal id (tenant-unique). */
99
+ id: string;
100
+ source: SourceRef;
101
+ /** Slug, unique per tenant. Primary read key for /products/<handle>. */
102
+ handle: string;
103
+ title: string;
104
+ description_html: string;
105
+ description_text: string;
106
+ vendor?: string;
107
+ product_type?: string;
108
+ tags: string[];
109
+ status: ProductStatus;
110
+ seo: SeoFields;
111
+ options: ProductOption[];
112
+ images: CatalogImage[];
113
+ variants: CatalogVariant[];
114
+ price_range: PriceRange;
115
+ /** Collection handles (or ids) this product belongs to. */
116
+ collections: string[];
117
+ metafields: Metafield[];
118
+ created_at: string; // ISO 8601
119
+ updated_at: string; // ISO 8601
120
+ migrated_at: string; // ISO 8601
121
+ /** Content hash for incremental sync / change detection. */
122
+ source_checksum: string;
123
+ }
124
+
125
+ /**
126
+ * A product review (full content), pulled from the merchant's reviews app
127
+ * (e.g. Stamped.io public widget API) during migration. The aggregate
128
+ * (count + average) also lives in product metafields; this is the per-review
129
+ * detail. Keyed to a product by handle in the reviews store.
130
+ */
131
+ export interface Review {
132
+ author: string;
133
+ rating: number; // 1–5
134
+ title?: string;
135
+ body: string;
136
+ date?: string; // ISO 8601
137
+ verifiedBuyer?: boolean;
138
+ }
139
+
140
+ export interface CatalogCollection {
141
+ tenant_id: string;
142
+ id: string;
143
+ source: SourceRef;
144
+ handle: string;
145
+ title: string;
146
+ description_html?: string;
147
+ description_text?: string;
148
+ image?: CatalogImage;
149
+ seo: SeoFields;
150
+ /** Resolved product handles (Phase 1 resolves rules to a static membership list). */
151
+ product_handles: string[];
152
+ /** For smart collections, the original rule (kept for re-resolution / audit). */
153
+ rule?: Record<string, unknown>;
154
+ position?: number;
155
+ created_at: string;
156
+ updated_at: string;
157
+ migrated_at: string;
158
+ source_checksum: string;
159
+ }