@tokenoftrust/storefront-runner 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +40 -0
  3. package/apps/storefront/.dev.vars.example +41 -0
  4. package/apps/storefront/.env.example +24 -0
  5. package/apps/storefront/astro.config.mjs +245 -0
  6. package/apps/storefront/env.d.ts +155 -0
  7. package/apps/storefront/lib/ensure-preview-reconcile-secret.sh +31 -0
  8. package/apps/storefront/lib/resolve-cf-token.sh +48 -0
  9. package/apps/storefront/lib/resolve-tot-credentials.sh +69 -0
  10. package/apps/storefront/migrations/0000_baseline.sql +54 -0
  11. package/apps/storefront/migrations/0001_products_fts.sql +9 -0
  12. package/apps/storefront/migrations/README.md +54 -0
  13. package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
  14. package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
  15. package/apps/storefront/migrations/meta/_journal.json +20 -0
  16. package/apps/storefront/package.json +41 -0
  17. package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
  18. package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
  19. package/apps/storefront/public/brand/northwind-og.svg +13 -0
  20. package/apps/storefront/public/favicon.svg +5 -0
  21. package/apps/storefront/public/js/dashboard-team.js +147 -0
  22. package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
  23. package/apps/storefront/src/components/Badge.astro +34 -0
  24. package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
  25. package/apps/storefront/src/components/CollectionCard.astro +56 -0
  26. package/apps/storefront/src/components/EmptyState.astro +35 -0
  27. package/apps/storefront/src/components/Img.astro +68 -0
  28. package/apps/storefront/src/components/ProductCard.astro +228 -0
  29. package/apps/storefront/src/components/Section.astro +39 -0
  30. package/apps/storefront/src/components/Seo.astro +70 -0
  31. package/apps/storefront/src/components/WidgetFrame.astro +34 -0
  32. package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
  33. package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
  34. package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
  35. package/apps/storefront/src/components/chrome/NavDropdown.astro +116 -0
  36. package/apps/storefront/src/components/chrome/SiteFooter.astro +181 -0
  37. package/apps/storefront/src/components/chrome/SiteHeader.astro +181 -0
  38. package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
  39. package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
  40. package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
  41. package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
  42. package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
  43. package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
  44. package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
  45. package/apps/storefront/src/components/home/Hero.astro +164 -0
  46. package/apps/storefront/src/components/islands/AgeGate.tsx +105 -0
  47. package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
  48. package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
  49. package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
  50. package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
  51. package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
  52. package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
  53. package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
  54. package/apps/storefront/src/components/islands/VariantSelector.tsx +378 -0
  55. package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
  56. package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
  57. package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
  58. package/apps/storefront/src/components/marketing/Faq.astro +37 -0
  59. package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
  60. package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
  61. package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
  62. package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
  63. package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
  64. package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
  65. package/apps/storefront/src/components/marketing/Steps.astro +36 -0
  66. package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
  67. package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
  68. package/apps/storefront/src/components/nav/Footer.astro +148 -0
  69. package/apps/storefront/src/components/nav/Header.astro +190 -0
  70. package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
  71. package/apps/storefront/src/components/plp/FacetSidebar.astro +146 -0
  72. package/apps/storefront/src/components/plp/Pagination.astro +99 -0
  73. package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
  74. package/apps/storefront/src/config/devTenants.ts +16 -0
  75. package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
  76. package/apps/storefront/src/config/resolver.ts +46 -0
  77. package/apps/storefront/src/config/tenants.ts +193 -0
  78. package/apps/storefront/src/layouts/Layout.astro +303 -0
  79. package/apps/storefront/src/lib/analytics/index.ts +47 -0
  80. package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
  81. package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
  82. package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
  83. package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
  84. package/apps/storefront/src/lib/auth/loginGate.ts +107 -0
  85. package/apps/storefront/src/lib/auth/route.ts +146 -0
  86. package/apps/storefront/src/lib/auth/session.ts +203 -0
  87. package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
  88. package/apps/storefront/src/lib/basePath.ts +41 -0
  89. package/apps/storefront/src/lib/cfImage.ts +55 -0
  90. package/apps/storefront/src/lib/chrome/model.ts +65 -0
  91. package/apps/storefront/src/lib/colors.ts +97 -0
  92. package/apps/storefront/src/lib/content-edit/client.ts +230 -0
  93. package/apps/storefront/src/lib/content-edit/index.ts +14 -0
  94. package/apps/storefront/src/lib/content-edit/route.ts +25 -0
  95. package/apps/storefront/src/lib/content-edit/types.ts +19 -0
  96. package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
  97. package/apps/storefront/src/lib/d1/catalog.ts +289 -0
  98. package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
  99. package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
  100. package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
  101. package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
  102. package/apps/storefront/src/lib/demoGate.ts +113 -0
  103. package/apps/storefront/src/lib/dev/ai-widget-client.js +127 -0
  104. package/apps/storefront/src/lib/dev/cliSignInCode.ts +132 -0
  105. package/apps/storefront/src/lib/diag/redact.ts +20 -0
  106. package/apps/storefront/src/lib/edgeCache.ts +128 -0
  107. package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
  108. package/apps/storefront/src/lib/env.ts +60 -0
  109. package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
  110. package/apps/storefront/src/lib/format.ts +29 -0
  111. package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
  112. package/apps/storefront/src/lib/imageAttrs.ts +55 -0
  113. package/apps/storefront/src/lib/jsonld.ts +136 -0
  114. package/apps/storefront/src/lib/maintenance.ts +71 -0
  115. package/apps/storefront/src/lib/memoryKv.ts +32 -0
  116. package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
  117. package/apps/storefront/src/lib/placeholder.ts +99 -0
  118. package/apps/storefront/src/lib/previewReload.ts +125 -0
  119. package/apps/storefront/src/lib/rawMarketingHtml.ts +133 -0
  120. package/apps/storefront/src/lib/search/index.ts +74 -0
  121. package/apps/storefront/src/lib/sections.ts +129 -0
  122. package/apps/storefront/src/lib/securityHeaders.ts +31 -0
  123. package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
  124. package/apps/storefront/src/lib/storyblok/provider.ts +524 -0
  125. package/apps/storefront/src/lib/tenantRouting.ts +91 -0
  126. package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
  127. package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
  128. package/apps/storefront/src/lib/tot/query.ts +187 -0
  129. package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
  130. package/apps/storefront/src/lib/url.ts +119 -0
  131. package/apps/storefront/src/lib/wishlist.ts +63 -0
  132. package/apps/storefront/src/middleware/index.ts +413 -0
  133. package/apps/storefront/src/pages/404.astro +61 -0
  134. package/apps/storefront/src/pages/[...slug].astro +90 -0
  135. package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
  136. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
  137. package/apps/storefront/src/pages/api/auth/send.ts +85 -0
  138. package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
  139. package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
  140. package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +64 -0
  141. package/apps/storefront/src/pages/api/csp-report.ts +25 -0
  142. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
  143. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
  144. package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +44 -0
  145. package/apps/storefront/src/pages/api/newsletter.ts +104 -0
  146. package/apps/storefront/src/pages/api/request-access.ts +172 -0
  147. package/apps/storefront/src/pages/auth/login.astro +149 -0
  148. package/apps/storefront/src/pages/auth/magic.astro +105 -0
  149. package/apps/storefront/src/pages/capabilities.astro +292 -0
  150. package/apps/storefront/src/pages/collections/[handle].astro +154 -0
  151. package/apps/storefront/src/pages/collections/index.astro +59 -0
  152. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
  153. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
  154. package/apps/storefront/src/pages/dashboard/index.astro +160 -0
  155. package/apps/storefront/src/pages/dev.astro +653 -0
  156. package/apps/storefront/src/pages/index.astro +290 -0
  157. package/apps/storefront/src/pages/llms.txt.ts +92 -0
  158. package/apps/storefront/src/pages/products/[handle].astro +443 -0
  159. package/apps/storefront/src/pages/robots.txt.ts +24 -0
  160. package/apps/storefront/src/pages/saved.astro +35 -0
  161. package/apps/storefront/src/pages/search.astro +150 -0
  162. package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
  163. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +646 -0
  164. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +85 -0
  165. package/apps/storefront/src/pages/style-guide/index.astro +94 -0
  166. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
  167. package/apps/storefront/src/styles/global.css +277 -0
  168. package/apps/storefront/src/themes/reference.ts +61 -0
  169. package/apps/storefront/src/themes/schema.ts +111 -0
  170. package/apps/storefront/src/themes/tenants/index.ts +51 -0
  171. package/apps/storefront/tsconfig.json +16 -0
  172. package/apps/storefront/vitest.config.ts +22 -0
  173. package/apps/storefront/wrangler.toml +175 -0
  174. package/package.json +23 -0
  175. package/packages/public-runtime/LICENSE +58 -0
  176. package/packages/public-runtime/package.json +22 -0
  177. package/packages/public-runtime/src/binary-path.ts +68 -0
  178. package/packages/public-runtime/src/capabilities.ts +89 -0
  179. package/packages/public-runtime/src/catalog-api.ts +81 -0
  180. package/packages/public-runtime/src/catalog-d1.ts +192 -0
  181. package/packages/public-runtime/src/csp.ts +109 -0
  182. package/packages/public-runtime/src/customization-preview.ts +197 -0
  183. package/packages/public-runtime/src/customization-reconcile.ts +63 -0
  184. package/packages/public-runtime/src/customization-runtime.ts +157 -0
  185. package/packages/public-runtime/src/customization-scripts.ts +64 -0
  186. package/packages/public-runtime/src/customization-versioning.ts +95 -0
  187. package/packages/public-runtime/src/foxy.ts +184 -0
  188. package/packages/public-runtime/src/index.ts +30 -0
  189. package/packages/public-runtime/src/product.ts +159 -0
  190. package/packages/public-runtime/src/search.ts +45 -0
  191. package/packages/public-runtime/src/tenant-assets.ts +444 -0
  192. package/packages/public-runtime/src/tenant.ts +243 -0
  193. package/packages/public-runtime/tsconfig.json +8 -0
  194. package/packages/public-runtime/vitest.config.ts +8 -0
  195. package/pnpm-workspace.yaml +7 -0
  196. package/scripts/build/copy-tenant-assets.mjs +69 -0
  197. package/scripts/tot-dev.mjs +439 -0
  198. package/tenants/bigdvapor/content/blog.json +52 -0
  199. package/tenants/bigdvapor/content/chrome.html +149 -0
  200. package/tenants/bigdvapor/content/chrome.json +47 -0
  201. package/tenants/bigdvapor/content/home.html +463 -0
  202. package/tenants/bigdvapor/content/home.json +73 -0
  203. package/tenants/bigdvapor/content/pages/about.json +5 -0
  204. package/tenants/bigdvapor/content/pages/privacy.json +6 -0
  205. package/tenants/bigdvapor/content/pages/shipping-returns.json +6 -0
  206. package/tenants/bigdvapor/content/pages-html/blogs/news.html +86 -0
  207. package/tenants/bigdvapor/content/pages-html/pages/about-us.html +106 -0
  208. package/tenants/bigdvapor/content/pages-html/pages/contact-us.html +61 -0
  209. package/tenants/bigdvapor/content/pages-html/pages/privacy-policy.html +72 -0
  210. package/tenants/bigdvapor/content/pages-html/pages/shipping-returns.html +172 -0
  211. package/tenants/bigdvapor/content/themes/bigd-navy.json +109 -0
  212. package/tenants/bigdvapor/public/img/brands/adjust-vape-disposable.png +0 -0
  213. package/tenants/bigdvapor/public/img/brands/again-vape-disposable.png +0 -0
  214. package/tenants/bigdvapor/public/img/brands/al-fakher-vape-disposable.png +0 -0
  215. package/tenants/bigdvapor/public/img/brands/arro-vape-disposable.png +0 -0
  216. package/tenants/bigdvapor/public/img/brands/cookies-vape-disposable.png +0 -0
  217. package/tenants/bigdvapor/public/img/brands/core-vape-disposable.png +0 -0
  218. package/tenants/bigdvapor/public/img/brands/cyclone-vape-disposable.png +0 -0
  219. package/tenants/bigdvapor/public/img/brands/dinner-lady-vape-disposable.png +0 -0
  220. package/tenants/bigdvapor/public/img/brands/extre-bar-vape-disposable.png +0 -0
  221. package/tenants/bigdvapor/public/img/brands/fifty-bar-vape-disposable.jpg +0 -0
  222. package/tenants/bigdvapor/public/img/brands/firerose-vape-disposable.jpg +0 -0
  223. package/tenants/bigdvapor/public/img/brands/flavor-beast-vape-disposable.jpg +0 -0
  224. package/tenants/bigdvapor/public/img/brands/foger-vape-disposable.png +0 -0
  225. package/tenants/bigdvapor/public/img/brands/kk-energy-vape-disposable.png +0 -0
  226. package/tenants/bigdvapor/public/img/brands/kumi-vape-disposable.png +0 -0
  227. package/tenants/bigdvapor/public/img/brands/lost-vape-dispoable.png +0 -0
  228. package/tenants/bigdvapor/public/img/brands/melo-labs-vape-disposable.png +0 -0
  229. package/tenants/bigdvapor/public/img/brands/mixo-vape-disposable.webp +0 -0
  230. package/tenants/bigdvapor/public/img/brands/one-tank-vape-disposable.jpg +0 -0
  231. package/tenants/bigdvapor/public/img/brands/oxbar-vape-disposable.webp +0 -0
  232. package/tenants/bigdvapor/public/img/brands/pillow-talk-vape-disposable.jpg +0 -0
  233. package/tenants/bigdvapor/public/img/brands/pod-king-vape-disposable.jpg +0 -0
  234. package/tenants/bigdvapor/public/img/brands/raz-vape-disposable.png +0 -0
  235. package/tenants/bigdvapor/public/img/hero-texas.jpg +0 -0
  236. package/tenants/bigdvapor/public/img/og.jpg +0 -0
  237. package/tenants/bigdvapor/public/img/pages/AdobeStock_202106312_web.jpg +0 -0
  238. package/tenants/bigdvapor/public/img/pages/CREW_web.jpg +0 -0
  239. package/tenants/bigdvapor/public/img/pages/Hero-Image.jpg +0 -0
  240. package/tenants/bigdvapor/public/img/tiles/adjust.png +0 -0
  241. package/tenants/bigdvapor/public/img/tiles/again.png +0 -0
  242. package/tenants/bigdvapor/public/img/tiles/al-fakher.png +0 -0
  243. package/tenants/bigdvapor/public/img/tiles/arro.png +0 -0
  244. package/tenants/bigdvapor/public/img/tiles/cookies.png +0 -0
  245. package/tenants/bigdvapor/public/img/tiles/core.png +0 -0
  246. package/tenants/bigdvapor/public/img/tiles/cyclone.png +0 -0
  247. package/tenants/bigdvapor/public/img/tiles/disposables.svg +23 -0
  248. package/tenants/bigdvapor/public/logo-wordmark.png +0 -0
  249. package/tenants/bigdvapor/public/pages/home.css +118 -0
  250. package/tenants/bigdvapor/public/pages/mkt.css +185 -0
  251. package/tenants/bigdvapor/public/pages/page.css +148 -0
  252. package/tenants/bigdvapor/public/themes/bigd-navy.css +73 -0
  253. package/tenants/bigdvapor/theme.json +12 -0
  254. package/tenants/giantvapes/content/chrome.html +152 -0
  255. package/tenants/giantvapes/content/chrome.json +94 -0
  256. package/tenants/giantvapes/content/home.html +194 -0
  257. package/tenants/giantvapes/content/home.json +50 -0
  258. package/tenants/giantvapes/content/pages/about.json +10 -0
  259. package/tenants/giantvapes/content/pages/privacy.json +6 -0
  260. package/tenants/giantvapes/content/pages/shipping-returns.json +6 -0
  261. package/tenants/giantvapes/content/pages-html/blogs/news.html +68 -0
  262. package/tenants/giantvapes/content/pages-html/pages/about-us.html +95 -0
  263. package/tenants/giantvapes/content/pages-html/pages/contact-us.html +68 -0
  264. package/tenants/giantvapes/content/pages-html/pages/privacy-policy.html +65 -0
  265. package/tenants/giantvapes/content/pages-html/pages/shipping-returns.html +77 -0
  266. package/tenants/giantvapes/content/themes/giant-navy.json +73 -0
  267. package/tenants/giantvapes/public/img/hero-suicide-bunny.jpg +0 -0
  268. package/tenants/giantvapes/public/img/hero.webp +0 -0
  269. package/tenants/giantvapes/public/img/og.jpg +0 -0
  270. package/tenants/giantvapes/public/logo-wordmark.png +0 -0
  271. package/tenants/giantvapes/public/pages/home.css +120 -0
  272. package/tenants/giantvapes/public/pages/mkt.css +185 -0
  273. package/tenants/giantvapes/public/pages/page.css +155 -0
  274. package/tenants/giantvapes/public/themes/giant-navy.css +76 -0
  275. package/tenants/giantvapes/theme.json +39 -0
  276. package/tenants/home/content/chrome.json +11 -0
  277. package/tenants/home/content/home.html +304 -0
  278. package/tenants/home/content/home.json +13 -0
  279. package/tenants/home/public/js/storefront.js +210 -0
  280. package/tenants/home/public/logo-wordmark.png +0 -0
  281. package/tenants/home/public/pages/storefront.css +212 -0
  282. package/tenants/home/theme.json +6 -0
  283. package/tenants/tokenoftrust/.tot/config.json +9 -0
  284. package/tenants/tokenoftrust/content/chrome.json +55 -0
  285. package/tenants/tokenoftrust/content/home.html +723 -0
  286. package/tenants/tokenoftrust/content/home.json +233 -0
  287. package/tenants/tokenoftrust/content/pages-html/careers.html +231 -0
  288. package/tenants/tokenoftrust/content/pages-html/company.html +275 -0
  289. package/tenants/tokenoftrust/content/pages-html/contact/contact-sales.html +188 -0
  290. package/tenants/tokenoftrust/content/pages-html/contact/startup-apply.html +206 -0
  291. package/tenants/tokenoftrust/content/pages-html/contact.html +178 -0
  292. package/tenants/tokenoftrust/content/pages-html/industries/adult-content-age-verification.html +286 -0
  293. package/tenants/tokenoftrust/content/pages-html/industries/cannabis.html +277 -0
  294. package/tenants/tokenoftrust/content/pages-html/pact-act-pricing.html +289 -0
  295. package/tenants/tokenoftrust/content/pages-html/pricing.html +483 -0
  296. package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-estimation.html +278 -0
  297. package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-gate.html +279 -0
  298. package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-verification.html +265 -0
  299. package/tenants/tokenoftrust/content/pages-html/product/age-assurance.html +173 -0
  300. package/tenants/tokenoftrust/content/pages-html/product/compliance/aml.html +214 -0
  301. package/tenants/tokenoftrust/content/pages-html/product/compliance/pact-act.html +226 -0
  302. package/tenants/tokenoftrust/content/pages-html/product/fraud-prevention.html +254 -0
  303. package/tenants/tokenoftrust/content/pages-html/product/social-profile-verification.html +212 -0
  304. package/tenants/tokenoftrust/content/pages-html/product/tax/excise-tax.html +213 -0
  305. package/tenants/tokenoftrust/content/pages-html/product/verification/aml.html +259 -0
  306. package/tenants/tokenoftrust/content/pages-html/product/verification/biometric-selfie.html +305 -0
  307. package/tenants/tokenoftrust/content/pages-html/product/verification/coverage.html +254 -0
  308. package/tenants/tokenoftrust/content/pages-html/product/verification/document-verification.html +306 -0
  309. package/tenants/tokenoftrust/content/pages-html/product/verification/electronic-identity-verification.html +304 -0
  310. package/tenants/tokenoftrust/content/pages-html/product/verification/government-id-verification.html +308 -0
  311. package/tenants/tokenoftrust/content/pages-html/product/verification.html +183 -0
  312. package/tenants/tokenoftrust/content/pages-html/product.html +228 -0
  313. package/tenants/tokenoftrust/content/pages-html/resources/integrations/bigcommerce-integration.html +220 -0
  314. package/tenants/tokenoftrust/content/pages-html/resources/integrations/shopify-integration.html +221 -0
  315. package/tenants/tokenoftrust/content/pages-html/resources/integrations/wordpress.html +220 -0
  316. package/tenants/tokenoftrust/content/pages-html/resources/integrations.html +172 -0
  317. package/tenants/tokenoftrust/content/pages-html/resources/refer-a-friend-program.html +317 -0
  318. package/tenants/tokenoftrust/content/pages-html/resources.html +177 -0
  319. package/tenants/tokenoftrust/content/pages-html/reviews.html +273 -0
  320. package/tenants/tokenoftrust/content/pages-html/solutions.html +292 -0
  321. package/tenants/tokenoftrust/content/pages-html/vlp-alcohol.html +289 -0
  322. package/tenants/tokenoftrust/content/pages-html/vlp-cigars-and-tobacco.html +286 -0
  323. package/tenants/tokenoftrust/content/pages-html/vlp-firearm-accessories.html +267 -0
  324. package/tenants/tokenoftrust/content/pages-html/vlp-peptides.html +273 -0
  325. package/tenants/tokenoftrust/content/themes/tot-navy.json +88 -0
  326. package/tenants/tokenoftrust/public/favicon.png +0 -0
  327. package/tenants/tokenoftrust/public/logo-icon.png +0 -0
  328. package/tenants/tokenoftrust/public/logo-wordmark.png +0 -0
  329. package/tenants/tokenoftrust/public/pages/company.css +108 -0
  330. package/tenants/tokenoftrust/public/pages/home.css +457 -0
  331. package/tenants/tokenoftrust/public/pages/mkt.css +482 -0
  332. package/tenants/tokenoftrust/public/pages/pricing.css +214 -0
  333. package/tenants/tokenoftrust/public/pages/solutions.css +66 -0
  334. package/tenants/tokenoftrust/public/themes/tot-navy-1.0.0.css +136 -0
  335. package/tenants/tokenoftrust/public/themes/tot-navy.css +358 -0
  336. package/tenants/tokenoftrust/theme.json +31 -0
  337. package/tsconfig.base.json +20 -0
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Runtime read path for published customizations (store→app wiring). Resolves,
3
+ * for a tenant + environment, the content of a published artifact from a simple
4
+ * key/value store — so the `{ live, test }` pointer actually selects what the
5
+ * storefront serves (the live domain → `live`, a gated preview → `test`).
6
+ *
7
+ * Pure + dependency-free: the caller injects a `get(key) => Promise<string|null>`
8
+ * (Cloudflare KV in prod, MemoryKv in dev), keeping this module storage-agnostic
9
+ * and unit-testable. The app layer parses the returned string into the right
10
+ * shape (e.g. DeepPartial<ThemeTokens>) and falls back to the build-time artifact
11
+ * when nothing is published (so an un-customized tenant is unaffected).
12
+ */
13
+ import {
14
+ channelFromEnv,
15
+ type ChannelName,
16
+ type PointerRecord,
17
+ type SiteVersionManifest,
18
+ } from "./customization-versioning.js";
19
+ import type { CustomizationEnv } from "./customization-preview.js";
20
+
21
+ export type KvGet = (key: string) => Promise<string | null>;
22
+
23
+ /** KV key layout for the customization store (mirrors the tenant-cache style). */
24
+ export function customizationPointerKey(tenantId: string): string {
25
+ return `cust:pointer:${tenantId}`;
26
+ }
27
+ export function customizationArtifactKey(
28
+ tenantId: string,
29
+ versionId: string,
30
+ ): string {
31
+ return `cust:artifact:${tenantId}:${versionId}`;
32
+ }
33
+ /** KV key for one immutable site-version manifest. */
34
+ export function siteVersionKey(tenantId: string, versionId: string): string {
35
+ return `cust:version:${tenantId}:${versionId}`;
36
+ }
37
+ /** KV key for a tenant's version-history index (list of summaries). */
38
+ export function siteVersionIndexKey(tenantId: string): string {
39
+ return `cust:versions:${tenantId}`;
40
+ }
41
+
42
+ /**
43
+ * Canonical artifact path for a tenant's theme override.
44
+ *
45
+ * Colocated layout: a tenant IS the repo-root directory `tenants/<id>/`, so the
46
+ * theme lives at `tenants/<id>/theme.json`. The storefront's published-content
47
+ * read overlay (provider.ts) keys KV artifacts by this path.
48
+ */
49
+ export function themeArtifactPath(tenantId: string): string {
50
+ return `tenants/${tenantId}/theme.json`;
51
+ }
52
+
53
+ /**
54
+ * Canonical artifact path for a tenant content file (e.g. "home.json"). Colocated
55
+ * under `tenants/<id>/content/` — see themeArtifactPath for the contract note.
56
+ */
57
+ export function contentArtifactPath(tenantId: string, relative: string): string {
58
+ return `tenants/${tenantId}/content/${relative}`;
59
+ }
60
+
61
+ async function readPointer(
62
+ get: KvGet,
63
+ tenantId: string,
64
+ ): Promise<PointerRecord | null> {
65
+ const raw = await get(customizationPointerKey(tenantId));
66
+ if (!raw) return null;
67
+ try {
68
+ return JSON.parse(raw) as PointerRecord;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * The `versionId` a channel currently points at, or null when the tenant has no
76
+ * channel pointer (i.e. is still on the legacy per-path model). Reads the pointer
77
+ * fresh; never throws.
78
+ */
79
+ export async function resolveChannelVersion(
80
+ get: KvGet,
81
+ tenantId: string,
82
+ channel: ChannelName,
83
+ ): Promise<string | null> {
84
+ const pointer = await readPointer(get, tenantId);
85
+ return pointer?.channels?.[channel] ?? null;
86
+ }
87
+
88
+ /** Read one immutable site-version manifest, or null. Never throws. */
89
+ export async function readSiteVersion(
90
+ get: KvGet,
91
+ tenantId: string,
92
+ versionId: string,
93
+ ): Promise<SiteVersionManifest | null> {
94
+ const raw = await get(siteVersionKey(tenantId, versionId));
95
+ if (!raw) return null;
96
+ try {
97
+ return JSON.parse(raw) as SiteVersionManifest;
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Resolve a single artifact's content via the VERSIONED path:
105
+ * channel → versionId → site-version manifest → content hash → bytes. Returns
106
+ * null (so the caller falls back to build-time) when the tenant has no channel
107
+ * pointer, the version manifest is missing, the path isn't in the snapshot, or
108
+ * the content is gone. Never throws.
109
+ */
110
+ export async function readVersionedArtifact(
111
+ get: KvGet,
112
+ tenantId: string,
113
+ channel: ChannelName,
114
+ path: string,
115
+ ): Promise<string | null> {
116
+ const versionId = await resolveChannelVersion(get, tenantId, channel);
117
+ if (!versionId) return null;
118
+ const manifest = await readSiteVersion(get, tenantId, versionId);
119
+ const contentHash = manifest?.artifacts?.[path];
120
+ if (!contentHash) return null;
121
+ return get(customizationArtifactKey(tenantId, contentHash));
122
+ }
123
+
124
+ /**
125
+ * Read the published artifact content for (tenant, env, path), or null when
126
+ * nothing is published (no pointer, no selection for that path, or no stored
127
+ * content). Never throws on malformed data — returns null so the caller falls
128
+ * back to the build-time artifact.
129
+ *
130
+ * Resolution is versioned-first with a legacy fallback:
131
+ * - If the tenant has a channel pointer for the resolved channel
132
+ * (`channelFromEnv(env)`), resolve via the immutable site version. A channel
133
+ * pointer is AUTHORITATIVE — if the snapshot omits this path we return null
134
+ * (fall back to build-time), never the stale legacy selection.
135
+ * - Otherwise fall back to the LEGACY per-artifact-path selection
136
+ * (`pointer[env][path]`), so tenants not yet cut over are unaffected.
137
+ */
138
+ export async function readPublishedArtifact(
139
+ get: KvGet,
140
+ tenantId: string,
141
+ env: CustomizationEnv,
142
+ path: string,
143
+ ): Promise<string | null> {
144
+ const pointer = await readPointer(get, tenantId);
145
+ if (!pointer) return null;
146
+
147
+ const channel = channelFromEnv(env);
148
+ if (pointer.channels?.[channel]) {
149
+ // Versioned model engaged for this channel — authoritative.
150
+ return readVersionedArtifact(get, tenantId, channel, path);
151
+ }
152
+
153
+ // Legacy per-path selection.
154
+ const versionId = pointer?.[env]?.[path];
155
+ if (!versionId) return null;
156
+ return get(customizationArtifactKey(tenantId, versionId));
157
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Tenant browser-script vocabulary the renderer needs: the slot / strategy /
3
+ * isolation constants, the CSP hash helper, and the sandboxed widget iframe
4
+ * attributes the renderer draws (`components/WidgetFrame.astro`).
5
+ *
6
+ * Dependency-free + Workers-portable (Web Crypto, btoa).
7
+ */
8
+
9
+ export const SCRIPT_SLOTS = [
10
+ "product.aside",
11
+ "home.section",
12
+ "global.footer",
13
+ ] as const;
14
+ export const SCRIPT_STRATEGIES = ["defer", "on-idle", "on-interaction"] as const;
15
+ export const SCRIPT_ISOLATIONS = ["sandbox", "inline"] as const;
16
+
17
+ export type ScriptSlot = (typeof SCRIPT_SLOTS)[number];
18
+ export type ScriptStrategy = (typeof SCRIPT_STRATEGIES)[number];
19
+ export type ScriptIsolation = (typeof SCRIPT_ISOLATIONS)[number];
20
+
21
+ export interface ScriptRegistration {
22
+ src: string;
23
+ slot: ScriptSlot;
24
+ strategy: ScriptStrategy;
25
+ isolation: ScriptIsolation;
26
+ }
27
+
28
+ /** The base64 `'sha256-…'` CSP source expression that pins this exact bundle. */
29
+ export async function cspHash(content: string | Uint8Array): Promise<string> {
30
+ const bytes =
31
+ typeof content === "string" ? new TextEncoder().encode(content) : content;
32
+ const digest = await crypto.subtle.digest(
33
+ "SHA-256",
34
+ bytes as unknown as BufferSource,
35
+ );
36
+ let bin = "";
37
+ for (const b of new Uint8Array(digest)) bin += String.fromCharCode(b);
38
+ return `'sha256-${btoa(bin)}'`;
39
+ }
40
+
41
+ /**
42
+ * Sandbox token list for tenant widgets: scripts only, **no
43
+ * `allow-same-origin`**. Omitting same-origin forces an opaque origin, so the
44
+ * widget is cross-origin to the page — it cannot read parent cookies/DOM, nor
45
+ * its own cookies/localStorage — even when served from the platform's own
46
+ * origin. That's why v1 needs no separate widget domain.
47
+ */
48
+ export const WIDGET_SANDBOX = "allow-scripts";
49
+
50
+ export interface WidgetFrameAttrs {
51
+ sandbox: string;
52
+ loading: "eager" | "lazy";
53
+ referrerpolicy: "no-referrer";
54
+ }
55
+
56
+ /** Iframe attributes for a sandboxed widget, derived from its load strategy. */
57
+ export function widgetFrameAttrs(strategy: ScriptStrategy): WidgetFrameAttrs {
58
+ return {
59
+ sandbox: WIDGET_SANDBOX,
60
+ // "defer" loads with the page; idle/interaction defer to lazy (near-viewport).
61
+ loading: strategy === "defer" ? "eager" : "lazy",
62
+ referrerpolicy: "no-referrer",
63
+ };
64
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Pointer/selection **types** for published customizations, as the storefront
3
+ * read path sees them: which content version is selected for `live` vs `test`,
4
+ * per artifact path. Types only — nothing here reads, writes, or mutates state.
5
+ */
6
+
7
+ /** Metadata for one immutable, content-addressed artifact version. */
8
+ export interface ArtifactVersion {
9
+ tenantId: string;
10
+ /** Repo-relative tenant-owned path (e.g. themes/tenants/<id>.theme.json). */
11
+ path: string;
12
+ /** Content-addressed id: sha256(content) hex. */
13
+ versionId: string;
14
+ size: number;
15
+ createdAt: string;
16
+ }
17
+
18
+ /** Which version of each artifact is selected for an environment. */
19
+ export type Selection = Record<string, string>;
20
+
21
+ /**
22
+ * A publish **channel**: the audience-facing surface a request resolves to.
23
+ * `live` is what production hosts serve; `preview` is what a gated preview host
24
+ * serves. (The middleware's `CustomizationEnv` "test" maps to the `preview`
25
+ * channel — see `channelFromEnv`.)
26
+ */
27
+ export type ChannelName = "live" | "preview";
28
+
29
+ /** Channel → versionId. Optional/partial: a channel may be unset. */
30
+ export type ChannelPointers = Partial<Record<ChannelName, string>>;
31
+
32
+ /**
33
+ * Per-tenant pointer record.
34
+ *
35
+ * - `live` / `test`: the LEGACY per-artifact-path selections (kept so tenants not
36
+ * yet cut over to the versioned model resolve exactly as before).
37
+ * - `channels`: the versioned-publish channel pointers (`live`/`preview` →
38
+ * a single site `versionId`). Additive + optional; when present it is
39
+ * authoritative for that channel and shadows the legacy selection.
40
+ * - `rev`: optimistic-concurrency token, incremented on every successful write.
41
+ */
42
+ export interface PointerRecord {
43
+ tenantId: string;
44
+ rev: number;
45
+ live: Selection;
46
+ test: Selection;
47
+ channels?: ChannelPointers;
48
+ }
49
+
50
+ export function emptyPointer(tenantId: string): PointerRecord {
51
+ return { tenantId, rev: 0, live: {}, test: {} };
52
+ }
53
+
54
+ /**
55
+ * Map the middleware's customization env to a publish channel. Production hosts
56
+ * resolve `live` → `live`; a gated preview resolves `test` → `preview`.
57
+ */
58
+ export function channelFromEnv(env: "live" | "test"): ChannelName {
59
+ return env === "live" ? "live" : "preview";
60
+ }
61
+
62
+ /**
63
+ * A reference to a tenant's public-asset bundle for a version. The asset lane
64
+ * stores the BYTES in R2 keyed by this same `versionId`; the site-version
65
+ * manifest carries only the reference (publish never writes asset bytes).
66
+ */
67
+ export interface AssetBundleRef {
68
+ versionId: string;
69
+ count?: number;
70
+ }
71
+
72
+ /**
73
+ * An immutable, content-addressed snapshot of a tenant's whole site, keyed by a
74
+ * single `versionId` (a Gitea commit sha, or a content hash when there is no
75
+ * commit). `artifacts` maps each artifact path to the sha256 of its content; the
76
+ * bytes live at `cust:artifact:<tenant>:<contentHash>` (the existing, deduped
77
+ * content store). `assets` references the public-asset bundle (bytes owned by the
78
+ * asset lane's R2). A version is written once and never mutated.
79
+ */
80
+ export interface SiteVersionManifest {
81
+ tenantId: string;
82
+ versionId: string;
83
+ source: "commit" | "hash";
84
+ createdAt: string;
85
+ artifacts: Record<string, string>;
86
+ assets?: AssetBundleRef;
87
+ }
88
+
89
+ /** Compact list-index entry for version history (KV has no cheap scan). */
90
+ export interface SiteVersionSummary {
91
+ versionId: string;
92
+ source: "commit" | "hash";
93
+ createdAt: string;
94
+ artifactCount: number;
95
+ }
@@ -0,0 +1,184 @@
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
+
46
+ /** Resolved, request-scoped commerce facts stashed on `Astro.locals`. */
47
+ export interface ResolvedFoxyCommerce extends FoxyCommerceConfig {
48
+ /** `https://<storeDomain>` — the loader + cart origin (for CSP + links). */
49
+ origin: string;
50
+ /** `https://<storeDomain>/cart` — the add-to-cart form action / cart link. */
51
+ cartUrl: string;
52
+ /** `https://cdn.foxycart.com/<storeSubDomain>/loader.js`. */
53
+ loaderSrc: string;
54
+ }
55
+
56
+ /** A single cart field before signing. `open` marks a user-editable field. */
57
+ export interface CartField {
58
+ name: string;
59
+ value: string;
60
+ open?: boolean;
61
+ }
62
+
63
+ /** A cart field ready to render (name may carry the `||hash[||open]` suffix). */
64
+ export interface RenderedCartField {
65
+ name: string;
66
+ value: string;
67
+ }
68
+
69
+ /**
70
+ * The add-to-cart payload for one variant, split so an island can render a form:
71
+ * `hidden` are fixed inputs; `quantityName` is the name to put on the visible
72
+ * quantity stepper (signed-and-`||open` in signed mode, plain "quantity" else).
73
+ */
74
+ export interface VariantCartPayload {
75
+ hidden: RenderedCartField[];
76
+ quantityName: string;
77
+ }
78
+
79
+ const FOXY_HOST_SUFFIX = ".foxycart.com";
80
+
81
+ /** Resolve the full request-scoped commerce facts from a tenant's config. */
82
+ export function resolveFoxyCommerce(
83
+ config: FoxyCommerceConfig,
84
+ ): ResolvedFoxyCommerce {
85
+ const storeDomain =
86
+ config.storeDomain ?? `${config.storeSubDomain}${FOXY_HOST_SUFFIX}`;
87
+ const origin = `https://${storeDomain}`;
88
+ return {
89
+ ...config,
90
+ storeDomain,
91
+ origin,
92
+ cartUrl: `${origin}/cart`,
93
+ loaderSrc: `https://cdn.foxycart.com/${config.storeSubDomain}/loader.js`,
94
+ };
95
+ }
96
+
97
+ /**
98
+ * The raw (unsigned) cart fields for a variant: name, price, code, image, the
99
+ * variant's options, and an editable quantity. `code` is the Foxy "productcode"
100
+ * — the salt every other field is hashed against — so it must be stable per SKU.
101
+ */
102
+ export function cartFieldsForVariant(
103
+ product: CatalogProduct,
104
+ variant: CatalogVariant,
105
+ ): { code: string; fields: CartField[] } {
106
+ const code = variant.sku && variant.sku.length ? variant.sku : variant.id;
107
+ const image = product.images.find((i) => i.id === variant.image_id)?.url ??
108
+ product.images[0]?.url;
109
+ const fields: CartField[] = [
110
+ { name: "name", value: variant.title || product.title },
111
+ { name: "price", value: variant.price.toFixed(2) },
112
+ { name: "code", value: code },
113
+ { name: "quantity", value: "1", open: true },
114
+ ];
115
+ if (image) fields.push({ name: "image", value: image });
116
+ // Variant options (Flavor, Nicotine strength, …) travel as product options so
117
+ // the cart line reflects the exact variant the shopper picked.
118
+ for (const [optName, optVal] of Object.entries(variant.option_values)) {
119
+ if (optName && optVal) fields.push({ name: optName, value: optVal });
120
+ }
121
+ return { code, fields };
122
+ }
123
+
124
+ /** Lowercase hex HMAC-SHA256(message) under `secret`, via WebCrypto. */
125
+ async function hmacHex(secret: string, message: string): Promise<string> {
126
+ const enc = new TextEncoder();
127
+ const key = await crypto.subtle.importKey(
128
+ "raw",
129
+ enc.encode(secret),
130
+ { name: "HMAC", hash: "SHA-256" },
131
+ false,
132
+ ["sign"],
133
+ );
134
+ const sig = await crypto.subtle.sign("HMAC", key, enc.encode(message));
135
+ return [...new Uint8Array(sig)]
136
+ .map((b) => b.toString(16).padStart(2, "0"))
137
+ .join("");
138
+ }
139
+
140
+ /**
141
+ * Sign one field for product `code`. Editable fields hash the sentinel
142
+ * `--OPEN--` and gain the `||open` suffix so Foxy accepts a user-entered value.
143
+ */
144
+ async function signField(
145
+ secret: string,
146
+ code: string,
147
+ field: CartField,
148
+ ): Promise<RenderedCartField> {
149
+ const valueToHash = field.open ? "--OPEN--" : field.value;
150
+ const hash = await hmacHex(secret, code + field.name + valueToHash);
151
+ const name = field.open
152
+ ? `${field.name}||${hash}||open`
153
+ : `${field.name}||${hash}`;
154
+ return { name, value: field.value };
155
+ }
156
+
157
+ /**
158
+ * Build the render-ready add-to-cart payload for a variant. In `unsigned-demo`
159
+ * mode fields pass through verbatim (no secret touched); in `signed` mode every
160
+ * field is HMAC-signed. Deterministic output → the signed form is edge-cacheable.
161
+ */
162
+ export async function buildVariantCartPayload(
163
+ product: CatalogProduct,
164
+ variant: CatalogVariant,
165
+ opts: { mode: FoxyCommerceConfig["mode"]; secret?: string },
166
+ ): Promise<VariantCartPayload> {
167
+ const { code, fields } = cartFieldsForVariant(product, variant);
168
+ const signed = opts.mode === "signed";
169
+ if (signed && !opts.secret) {
170
+ throw new Error(
171
+ "buildVariantCartPayload: signed mode requires a cart secret",
172
+ );
173
+ }
174
+
175
+ const rendered: RenderedCartField[] = signed
176
+ ? await Promise.all(fields.map((f) => signField(opts.secret!, code, f)))
177
+ : fields.map((f) => ({ name: f.name, value: f.value }));
178
+
179
+ // Split the quantity field out: the island puts it on the visible stepper.
180
+ const qtyIdx = fields.findIndex((f) => f.name === "quantity");
181
+ const quantityName = rendered[qtyIdx]?.name ?? "quantity";
182
+ const hidden = rendered.filter((_, i) => i !== qtyIdx);
183
+ return { hidden, quantityName };
184
+ }
@@ -0,0 +1,30 @@
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 "./capabilities.js";
20
+ export * from "./catalog-api.js";
21
+ export * from "./search.js";
22
+ export * from "./catalog-d1.js";
23
+ export * from "./csp.js";
24
+ export * from "./customization-preview.js";
25
+ export * from "./customization-runtime.js";
26
+ export * from "./customization-versioning.js";
27
+ export * from "./customization-scripts.js";
28
+ export * from "./customization-reconcile.js";
29
+ export * from "./tenant-assets.js";
30
+ export * from "./binary-path.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
+ }