@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,107 @@
1
+ /**
2
+ * Login-gate decision (pure) — the "Sign in with ToT" enforcement mode for a
3
+ * gated demo host, the eventual replacement for the interim Basic-Auth password
4
+ * (see `@/lib/demoGate`). Same `DEMO_GATE_HOSTS` list, switched by `DEMO_GATE_MODE`:
5
+ *
6
+ * DEMO_GATE_MODE = "password" → interim Basic-Auth gate (evaluateDemoGate)
7
+ * DEMO_GATE_MODE = "login" → this: redirect to /auth/login unless a valid,
8
+ * resource-bound tot_session cookie is present.
9
+ *
10
+ * This module is PURE (no KV, no cookies, no env I/O): the middleware reads the
11
+ * session (KV) and passes the resolved record in. Keeping the decision pure makes
12
+ * the exemption + redirect logic unit-testable and keeps the two gate modes from
13
+ * drifting apart. The auth surface itself (/auth/*, /api/auth/*) is always exempt
14
+ * so a visitor can actually sign in.
15
+ */
16
+ import { hostInGateList } from "@/lib/demoGate";
17
+ import { viewerCanAccess } from "@/lib/dashboard/tenantSelection";
18
+ import type { SessionRecord } from "./session.js";
19
+
20
+ export type GateMode = "password" | "login";
21
+
22
+ /**
23
+ * The platform's own "self" home tenant (tenants.ts `HOME_TENANT`, registry
24
+ * default). It is NEVER login-gated — `storefront.tokenoftrust.store` renders its
25
+ * public home so people can browse + sign up. Every OTHER tenant on a gated host
26
+ * (path-prefix or per-tenant subdomain) is gated behind sign-in.
27
+ */
28
+ export const PUBLIC_HOME_TENANT_ID = "home";
29
+
30
+ /**
31
+ * Whether a (federated) viewer session admits the gated HOST — i.e. the signed-in
32
+ * user holds a membership for the host's tenant (`hostResource`, the host's
33
+ * appDomain). This UNIFIES the preview-host gate onto the single federated session:
34
+ * federated sign-in mints a multi-tenant session whose `aud`/`resource` is the
35
+ * storefront app (not a tenant), so the old resource-scoped session match never
36
+ * fit; membership in `tenants[]` is the right test. Deny-closed: no session, or a
37
+ * session without a membership for `hostResource`, is not admitted. (Backward-
38
+ * compatible with a legacy resource-scoped session, whose `tenants[]` still lists
39
+ * its own resource.)
40
+ */
41
+ export function sessionAdmitsHost(
42
+ record: Pick<SessionRecord, "tenants" | "staffSelection" | "staff"> | null,
43
+ hostResource: string,
44
+ nowSeconds: number = Math.floor(Date.now() / 1000),
45
+ ): boolean {
46
+ if (!record) return false;
47
+ // ToT STAFF get in everywhere: any session carrying a verified `staff` scope is
48
+ // admitted to EVERY gated demo host with just a login — no per-vendor selection
49
+ // needed. This is a GATE-level admission for demo BROWSING only; dashboard-action
50
+ // authorization (viewerCanAccess) stays membership/selection-bound + audited.
51
+ if (Array.isArray(record.staff) && record.staff.length > 0) return true;
52
+ // Otherwise: a membership OR a live ToT-staff selection for this exact host admits
53
+ // — the staff-selection path is distinct from tenants[] (never a synthesized member).
54
+ return viewerCanAccess(record.tenants, hostResource, {
55
+ selection: record.staffSelection,
56
+ nowSeconds,
57
+ });
58
+ }
59
+
60
+ /** Parse `DEMO_GATE_MODE` (default "password"). */
61
+ export function parseGateMode(raw: string | null | undefined): GateMode {
62
+ return (raw ?? "").trim().toLowerCase() === "login" ? "login" : "password";
63
+ }
64
+
65
+ /**
66
+ * Paths that MUST stay reachable without a session, or the visitor could never
67
+ * authenticate: the login page and the send/verify endpoints. Everything else on
68
+ * a login-gated host requires a session.
69
+ */
70
+ export function isAuthPath(pathname: string): boolean {
71
+ return pathname === "/auth/login" || pathname.startsWith("/api/auth/");
72
+ }
73
+
74
+ export type LoginGateDecision =
75
+ | { action: "pass" }
76
+ | { action: "redirect"; location: string };
77
+
78
+ export interface LoginGateInput {
79
+ host: string;
80
+ pathname: string;
81
+ search: string;
82
+ mode: GateMode;
83
+ /** The gated-host list (`DEMO_GATE_HOSTS`). */
84
+ gatedHosts: string | null | undefined;
85
+ /** True when a valid, resource-bound session was resolved for this request. */
86
+ hasValidSession: boolean;
87
+ /**
88
+ * True when the resolved tenant is the platform's own public home (self) —
89
+ * exempt from the gate so it renders publicly. See {@link PUBLIC_HOME_TENANT_ID}.
90
+ */
91
+ tenantExempt?: boolean;
92
+ }
93
+
94
+ /**
95
+ * Decide what the login gate should do. Returns `pass` when the host isn't
96
+ * login-gated, the path is an auth path, or a valid session exists; otherwise a
97
+ * `redirect` to /auth/login carrying the intended destination as `next`.
98
+ */
99
+ export function evaluateLoginGate(input: LoginGateInput): LoginGateDecision {
100
+ if (input.mode !== "login") return { action: "pass" };
101
+ if (!hostInGateList(input.host, input.gatedHosts)) return { action: "pass" };
102
+ if (input.tenantExempt) return { action: "pass" };
103
+ if (isAuthPath(input.pathname)) return { action: "pass" };
104
+ if (input.hasValidSession) return { action: "pass" };
105
+ const next = input.pathname + (input.search ?? "");
106
+ return { action: "redirect", location: `/auth/login?next=${encodeURIComponent(next)}` };
107
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Shared helpers for the `/auth/*` + `/api/auth/*` sign-in surface. Kept out of
3
+ * the pure session/client modules so those stay transport- and Astro-free and
4
+ * unit-test against the in-memory impls.
5
+ *
6
+ * The flow (no-JS-friendly, all on the tenant's own branded host):
7
+ * GET /auth/login → email card (step 1) / code card (step 2)
8
+ * POST /api/auth/send {email} → tot20 sendAccessCode → 303 back to step 2
9
+ * POST /api/auth/verify{email,code}→ tot20 verifyAccessCode → session cookie → 303 next
10
+ *
11
+ * The gate ("login" mode) redirects unauthenticated viewers of a gated host to
12
+ * /auth/login and lets a valid `tot_session` cookie back through. The `resource`
13
+ * (the tot20 appDomain the grant is checked against) comes from the resolved
14
+ * tenant — never from request input.
15
+ */
16
+ import type { APIContext } from "astro";
17
+ import { readKv } from "@/lib/env";
18
+ import { createTotAccessClient, type TotAccessClient } from "./totAccessClient.js";
19
+ import {
20
+ KvSessionStore,
21
+ readSession,
22
+ type SessionRecord,
23
+ type SessionStore,
24
+ SESSION_COOKIE,
25
+ } from "./session.js";
26
+ import {
27
+ getIdentityVerifier as buildIdentityVerifier,
28
+ type IdentityAssertionVerifier,
29
+ } from "./identityToken.js";
30
+
31
+ /** Permissive email shape — tot20 is the real authority on eligibility. */
32
+ export const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
33
+
34
+ /**
35
+ * The tot20 `resource` (appDomain) the email's roles are checked against for this
36
+ * host. Derived from the resolved tenant, never from input.
37
+ *
38
+ * INTEGRATION NOTE: which tenant field equals tot20's appDomain for a store is
39
+ * confirmed at integration (task #4). We use `appDomain` (the tenant's app
40
+ * identity) and allow a per-tenant override via `commerce`/config later.
41
+ */
42
+ export function resolveResource(context: APIContext): string | null {
43
+ const t = context.locals.tenant;
44
+ if (!t) return null;
45
+ return t.appDomain || t.tenant_id || null;
46
+ }
47
+
48
+ /** The access client from env, or null when the integration isn't configured. */
49
+ export async function getAccessClient(): Promise<TotAccessClient | null> {
50
+ try {
51
+ return await createTotAccessClient();
52
+ } catch {
53
+ return null; // unconfigured → routes return 503
54
+ }
55
+ }
56
+
57
+ /** KV-backed session store, or null in runtimes without the SESSION binding (dev). */
58
+ export async function getSessionStore(): Promise<SessionStore | null> {
59
+ const kv = await readKv("SESSION");
60
+ return kv ? new KvSessionStore(kv) : null;
61
+ }
62
+
63
+ /**
64
+ * Read the signed-in viewer's FULL session (email + all tenant memberships), or
65
+ * null when there's no valid session. Unlike the gate's read, this is NOT scoped
66
+ * to a single host `resource` — the vendor dashboard needs every membership in
67
+ * `tenants[]` to route (single → in, multiple → picker) and to authorize actions
68
+ * against the SELECTED vendor. Returns null in runtimes without the SESSION KV.
69
+ */
70
+ export async function readViewerSession(
71
+ context: { cookies: APIContext["cookies"] },
72
+ ): Promise<SessionRecord | null> {
73
+ const store = await getSessionStore();
74
+ if (!store) return null;
75
+ const sid = context.cookies.get(SESSION_COOKIE)?.value ?? null;
76
+ return readSession(store, sid, Math.floor(Date.now() / 1000));
77
+ }
78
+
79
+ /**
80
+ * The session store + the current request's opaque session id, for callers that
81
+ * need to MUTATE the stored record (e.g. stamping a staff vendor selection). Null
82
+ * when there's no SESSION KV (dev) or no session cookie. Kept alongside
83
+ * {@link readViewerSession} so mutation and read share the same store/cookie seam.
84
+ */
85
+ export async function getSessionMutationHandle(
86
+ context: { cookies: APIContext["cookies"] },
87
+ ): Promise<{ store: SessionStore; id: string } | null> {
88
+ const store = await getSessionStore();
89
+ if (!store) return null;
90
+ const id = context.cookies.get(SESSION_COOKIE)?.value ?? null;
91
+ if (!id) return null;
92
+ return { store, id };
93
+ }
94
+
95
+ /**
96
+ * The offline `id_token` verifier for this host's `resource`, or null when the
97
+ * tot JWKS host (`TOT_CORE_URL`) isn't configured. Derives the JWKS URL from the
98
+ * same tot API base the access-code client posts to; jose caches the fetch so
99
+ * verification survives tot being unreachable after sign-in (Phase-3 property).
100
+ */
101
+ export async function getVerifier(resource: string): Promise<IdentityAssertionVerifier | null> {
102
+ return buildIdentityVerifier(resource);
103
+ }
104
+
105
+ export { getVerifier as getIdentityVerifier };
106
+
107
+ /** Set the session cookie (HttpOnly/Secure/SameSite=Lax) for `maxAgeSeconds`. */
108
+ export function setSessionCookie(context: APIContext, id: string, maxAgeSeconds: number): void {
109
+ context.cookies.set(SESSION_COOKIE, id, {
110
+ httpOnly: true,
111
+ secure: true,
112
+ sameSite: "lax",
113
+ path: "/",
114
+ maxAge: maxAgeSeconds,
115
+ });
116
+ }
117
+
118
+ /** Clear the session cookie (logout). */
119
+ export function clearSessionCookie(context: APIContext): void {
120
+ context.cookies.delete(SESSION_COOKIE, { path: "/" });
121
+ }
122
+
123
+ /**
124
+ * Sanitize a `next` redirect target to a same-origin PATH only (starts with a
125
+ * single "/", not "//" or a scheme) — closes the open-redirect hole. Falls back
126
+ * to "/".
127
+ */
128
+ export function safeNext(next: string | null | undefined): string {
129
+ if (!next) return "/";
130
+ if (!next.startsWith("/") || next.startsWith("//")) return "/";
131
+ return next;
132
+ }
133
+
134
+ /** Redirect back to the login page carrying step-2 state (email/error), no-JS-safe. */
135
+ export function loginRedirect(
136
+ context: APIContext,
137
+ params: { sent?: boolean; email?: string; error?: string; next?: string },
138
+ ): Response {
139
+ const q = new URLSearchParams();
140
+ if (params.sent) q.set("sent", "1");
141
+ if (params.email) q.set("email", params.email);
142
+ if (params.error) q.set("error", params.error);
143
+ if (params.next && params.next !== "/") q.set("next", params.next);
144
+ const qs = q.toString();
145
+ return context.redirect(`/auth/login${qs ? `?${qs}` : ""}`, 303);
146
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Signed-in viewer session for the demo access gate (Track: "Sign in with ToT").
3
+ *
4
+ * After a visitor verifies a one-time email code (tot20 `verifyAccessCode`), we
5
+ * hold a server-side session so the gate can let them back in without re-auth.
6
+ * The session is **KV-backed** (revocable + no signing-key handling): the cookie
7
+ * carries only an opaque random id; the record (email, resource, roles) lives in
8
+ * the `SESSION` KV namespace with a TTL. Deleting the KV entry (or letting it
9
+ * expire) revokes access — a static bearer password can't do that.
10
+ *
11
+ * Renderer-agnostic and runtime-portable (WebCrypto + a small store seam). The
12
+ * store is an interface so routes/gate use `KvSessionStore` in the worker and an
13
+ * in-memory impl in tests. Nothing here reads env or touches cookies — the
14
+ * routes own cookie I/O (via Astro `context.cookies`) and the gate owns the
15
+ * host↔resource check.
16
+ */
17
+
18
+ /** The cookie the browser carries; value is an opaque session id (not the record). */
19
+ export const SESSION_COOKIE = "tot_session";
20
+
21
+ /**
22
+ * A ToT-staff viewer's ACTIVE vendor selection — the two-phase-admission marker
23
+ * (#8425 staff cross-vendor access). This is a DISTINCT access path from
24
+ * `tenants[]`: a staff user isn't a member of a bounded set of vendors, so we
25
+ * NEVER synthesize a `tenants[]` entry for them. Instead, after the staff viewer
26
+ * explicitly searches + enters a vendor and tot20's `staffVendorAccess` writes
27
+ * the server-side audit row, we stamp THIS marker on the session (KV) scoped to
28
+ * the returned staffRoles `capability` and a short `exp`. The dashboard guard and
29
+ * login gate consult it as a separate branch from membership.
30
+ */
31
+ export interface StaffSelection {
32
+ /** The vendor appDomain the staff viewer was admitted to (exact match only). */
33
+ resource: string;
34
+ /** staffRoles-scoped capability tot20 returned for this admission. */
35
+ capability: string;
36
+ /** Expiry (epoch seconds) — short-lived; re-checked on every gate/guard read. */
37
+ exp: number;
38
+ }
39
+
40
+ /** Default session lifetime — a demo viewing session, revocable via KV. */
41
+ export const DEFAULT_SESSION_TTL_SECONDS = 7 * 24 * 3600;
42
+
43
+ /**
44
+ * What we know about a signed-in viewer.
45
+ *
46
+ * Phase 3: `email`/`resource`/`roles`/`capability` are now derived from tot's
47
+ * OFFLINE-VERIFIED `id_token` claims (see `@/lib/auth/identityToken`
48
+ * `sessionFromClaims`), not from trusting tot's flat `/verify` JSON. `capability`
49
+ * is the coarse standing on `resource` (owner|admin|member). `tenants`/`staff`
50
+ * are the multi-tenant claims carried in the verified token (present when the
51
+ * session was minted via the Phase-3 path; optional for backward compatibility).
52
+ */
53
+ export interface SessionRecord {
54
+ /** Verified email (lowercased) — the principal that passed the grant check. */
55
+ email: string;
56
+ /** The resource (appDomain/tenant, e.g. "bigdvapor") this session authorizes. */
57
+ resource: string;
58
+ /** Display roles for `resource` (Phase 3: the coarse role from verified claims). */
59
+ roles: string[];
60
+ /** Coarse capability on `resource` (owner|admin|member; legacy: developer|inviter|staff|none). */
61
+ capability: string;
62
+ /** All tenant memberships from the verified assertion (policy input). Phase-3 path only. */
63
+ tenants?: { resource: string; role: string }[];
64
+ /** ToT-staff scope from the verified assertion (sandboxed; grants nothing today). Phase-3 path only. */
65
+ staff?: string[];
66
+ /**
67
+ * A staff viewer's active, explicitly-entered vendor selection (the two-phase
68
+ * admission marker). Present ONLY after `/api/dashboard/enter-vendor` succeeds;
69
+ * DISTINCT from `tenants[]` — never a synthesized membership. See {@link StaffSelection}.
70
+ */
71
+ staffSelection?: StaffSelection;
72
+ /** Issued-at, epoch seconds. */
73
+ createdAt: number;
74
+ /** Expiry, epoch seconds. The store TTL mirrors this; we re-check on read. */
75
+ exp: number;
76
+ }
77
+
78
+ /** Minimal KV-ish seam so this unit-tests without a live KVNamespace. */
79
+ export interface SessionStore {
80
+ put(id: string, value: string, ttlSeconds: number): Promise<void>;
81
+ get(id: string): Promise<string | null>;
82
+ delete(id: string): Promise<void>;
83
+ }
84
+
85
+ /** Namespacing so session ids never collide with other SESSION-KV keys. */
86
+ const KEY_PREFIX = "authsession:";
87
+
88
+ /** A `SessionStore` over a Cloudflare `KVNamespace` (the `SESSION` binding). */
89
+ export class KvSessionStore implements SessionStore {
90
+ constructor(private readonly kv: KVNamespace) {}
91
+ async put(id: string, value: string, ttlSeconds: number): Promise<void> {
92
+ // KV requires expirationTtl >= 60; clamp so short TTLs in tests/dev don't throw.
93
+ await this.kv.put(KEY_PREFIX + id, value, {
94
+ expirationTtl: Math.max(60, Math.floor(ttlSeconds)),
95
+ });
96
+ }
97
+ async get(id: string): Promise<string | null> {
98
+ return this.kv.get(KEY_PREFIX + id);
99
+ }
100
+ async delete(id: string): Promise<void> {
101
+ await this.kv.delete(KEY_PREFIX + id);
102
+ }
103
+ }
104
+
105
+ /** A cryptographically-random, URL-safe session id (128 bits). */
106
+ export function newSessionId(): string {
107
+ const bytes = new Uint8Array(16);
108
+ crypto.getRandomValues(bytes);
109
+ let bin = "";
110
+ for (const b of bytes) bin += String.fromCharCode(b);
111
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
112
+ }
113
+
114
+ /**
115
+ * Create a session for a verified viewer: mint an id, persist the record with a
116
+ * matching TTL, return the id (the caller sets it as the cookie). `nowSeconds`
117
+ * is injected so this stays pure/testable.
118
+ */
119
+ export async function createSession(
120
+ store: SessionStore,
121
+ fields: Omit<SessionRecord, "createdAt" | "exp">,
122
+ ttlSeconds: number,
123
+ nowSeconds: number,
124
+ ): Promise<{ id: string; record: SessionRecord }> {
125
+ const record: SessionRecord = {
126
+ ...fields,
127
+ email: fields.email.toLowerCase(),
128
+ createdAt: nowSeconds,
129
+ exp: nowSeconds + ttlSeconds,
130
+ };
131
+ const id = newSessionId();
132
+ await store.put(id, JSON.stringify(record), ttlSeconds);
133
+ return { id, record };
134
+ }
135
+
136
+ /**
137
+ * Read + validate a session by id. Returns the record only when it exists, is
138
+ * well-formed, has not expired, and (when `resource` is given) authorizes THAT
139
+ * resource — so a session minted for tenant A can never open tenant B's gate.
140
+ * A malformed or expired record is best-effort deleted.
141
+ */
142
+ export async function readSession(
143
+ store: SessionStore,
144
+ id: string | null | undefined,
145
+ nowSeconds: number,
146
+ resource?: string,
147
+ ): Promise<SessionRecord | null> {
148
+ if (!id) return null;
149
+ const raw = await store.get(id);
150
+ if (!raw) return null;
151
+ let record: SessionRecord;
152
+ try {
153
+ record = JSON.parse(raw) as SessionRecord;
154
+ } catch {
155
+ await store.delete(id);
156
+ return null;
157
+ }
158
+ if (
159
+ !record ||
160
+ typeof record.email !== "string" ||
161
+ typeof record.resource !== "string" ||
162
+ typeof record.exp !== "number"
163
+ ) {
164
+ await store.delete(id);
165
+ return null;
166
+ }
167
+ if (record.exp <= nowSeconds) {
168
+ await store.delete(id);
169
+ return null;
170
+ }
171
+ if (resource && record.resource !== resource) return null;
172
+ return record;
173
+ }
174
+
175
+ /**
176
+ * Read a session, apply `mutate`, and re-persist it under the SAME id with the
177
+ * record's REMAINING lifetime (`exp - now`) as the store TTL — so stamping a
178
+ * staff selection (or any field) never extends the session past its original
179
+ * expiry. Returns the updated record, or null when there's no valid session to
180
+ * mutate (expired/missing/malformed are handled by {@link readSession}). The
181
+ * caller must NOT change `exp` in `mutate` unless it also intends to change TTL.
182
+ */
183
+ export async function updateSession(
184
+ store: SessionStore,
185
+ id: string | null | undefined,
186
+ mutate: (record: SessionRecord) => SessionRecord,
187
+ nowSeconds: number,
188
+ ): Promise<SessionRecord | null> {
189
+ const current = await readSession(store, id, nowSeconds);
190
+ if (!current || !id) return null;
191
+ const next = mutate({ ...current });
192
+ const ttl = Math.max(1, next.exp - nowSeconds);
193
+ await store.put(id, JSON.stringify(next), ttl);
194
+ return next;
195
+ }
196
+
197
+ /** Revoke a session (logout / admin kill). Idempotent. */
198
+ export async function revokeSession(
199
+ store: SessionStore,
200
+ id: string | null | undefined,
201
+ ): Promise<void> {
202
+ if (id) await store.delete(id);
203
+ }