@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.
- package/LICENSE +58 -0
- package/README.md +40 -0
- package/apps/storefront/.dev.vars.example +41 -0
- package/apps/storefront/.env.example +24 -0
- package/apps/storefront/astro.config.mjs +245 -0
- package/apps/storefront/env.d.ts +155 -0
- package/apps/storefront/lib/ensure-preview-reconcile-secret.sh +31 -0
- package/apps/storefront/lib/resolve-cf-token.sh +48 -0
- package/apps/storefront/lib/resolve-tot-credentials.sh +69 -0
- package/apps/storefront/migrations/0000_baseline.sql +54 -0
- package/apps/storefront/migrations/0001_products_fts.sql +9 -0
- package/apps/storefront/migrations/README.md +54 -0
- package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
- package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
- package/apps/storefront/migrations/meta/_journal.json +20 -0
- package/apps/storefront/package.json +41 -0
- package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
- package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
- package/apps/storefront/public/brand/northwind-og.svg +13 -0
- package/apps/storefront/public/favicon.svg +5 -0
- package/apps/storefront/public/js/dashboard-team.js +147 -0
- package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
- package/apps/storefront/src/components/Badge.astro +34 -0
- package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
- package/apps/storefront/src/components/CollectionCard.astro +56 -0
- package/apps/storefront/src/components/EmptyState.astro +35 -0
- package/apps/storefront/src/components/Img.astro +68 -0
- package/apps/storefront/src/components/ProductCard.astro +228 -0
- package/apps/storefront/src/components/Section.astro +39 -0
- package/apps/storefront/src/components/Seo.astro +70 -0
- package/apps/storefront/src/components/WidgetFrame.astro +34 -0
- package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
- package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
- package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
- package/apps/storefront/src/components/chrome/NavDropdown.astro +116 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +181 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +181 -0
- package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
- package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
- package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
- package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
- package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
- package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
- package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
- package/apps/storefront/src/components/home/Hero.astro +164 -0
- package/apps/storefront/src/components/islands/AgeGate.tsx +105 -0
- package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
- package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
- package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
- package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
- package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
- package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
- package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
- package/apps/storefront/src/components/islands/VariantSelector.tsx +378 -0
- package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
- package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
- package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
- package/apps/storefront/src/components/marketing/Faq.astro +37 -0
- package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
- package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
- package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
- package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
- package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
- package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
- package/apps/storefront/src/components/marketing/Steps.astro +36 -0
- package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
- package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
- package/apps/storefront/src/components/nav/Footer.astro +148 -0
- package/apps/storefront/src/components/nav/Header.astro +190 -0
- package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
- package/apps/storefront/src/components/plp/FacetSidebar.astro +146 -0
- package/apps/storefront/src/components/plp/Pagination.astro +99 -0
- package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
- package/apps/storefront/src/config/devTenants.ts +16 -0
- package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
- package/apps/storefront/src/config/resolver.ts +46 -0
- package/apps/storefront/src/config/tenants.ts +193 -0
- package/apps/storefront/src/layouts/Layout.astro +303 -0
- package/apps/storefront/src/lib/analytics/index.ts +47 -0
- package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
- package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
- package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
- package/apps/storefront/src/lib/auth/loginGate.ts +107 -0
- package/apps/storefront/src/lib/auth/route.ts +146 -0
- package/apps/storefront/src/lib/auth/session.ts +203 -0
- package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
- package/apps/storefront/src/lib/basePath.ts +41 -0
- package/apps/storefront/src/lib/cfImage.ts +55 -0
- package/apps/storefront/src/lib/chrome/model.ts +65 -0
- package/apps/storefront/src/lib/colors.ts +97 -0
- package/apps/storefront/src/lib/content-edit/client.ts +230 -0
- package/apps/storefront/src/lib/content-edit/index.ts +14 -0
- package/apps/storefront/src/lib/content-edit/route.ts +25 -0
- package/apps/storefront/src/lib/content-edit/types.ts +19 -0
- package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
- package/apps/storefront/src/lib/d1/catalog.ts +289 -0
- package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
- package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
- package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
- package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
- package/apps/storefront/src/lib/demoGate.ts +113 -0
- package/apps/storefront/src/lib/dev/ai-widget-client.js +127 -0
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +132 -0
- package/apps/storefront/src/lib/diag/redact.ts +20 -0
- package/apps/storefront/src/lib/edgeCache.ts +128 -0
- package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
- package/apps/storefront/src/lib/env.ts +60 -0
- package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
- package/apps/storefront/src/lib/format.ts +29 -0
- package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
- package/apps/storefront/src/lib/imageAttrs.ts +55 -0
- package/apps/storefront/src/lib/jsonld.ts +136 -0
- package/apps/storefront/src/lib/maintenance.ts +71 -0
- package/apps/storefront/src/lib/memoryKv.ts +32 -0
- package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
- package/apps/storefront/src/lib/placeholder.ts +99 -0
- package/apps/storefront/src/lib/previewReload.ts +125 -0
- package/apps/storefront/src/lib/rawMarketingHtml.ts +133 -0
- package/apps/storefront/src/lib/search/index.ts +74 -0
- package/apps/storefront/src/lib/sections.ts +129 -0
- package/apps/storefront/src/lib/securityHeaders.ts +31 -0
- package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
- package/apps/storefront/src/lib/storyblok/provider.ts +524 -0
- package/apps/storefront/src/lib/tenantRouting.ts +91 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
- package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
- package/apps/storefront/src/lib/tot/query.ts +187 -0
- package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
- package/apps/storefront/src/lib/url.ts +119 -0
- package/apps/storefront/src/lib/wishlist.ts +63 -0
- package/apps/storefront/src/middleware/index.ts +413 -0
- package/apps/storefront/src/pages/404.astro +61 -0
- package/apps/storefront/src/pages/[...slug].astro +90 -0
- package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
- package/apps/storefront/src/pages/api/auth/send.ts +85 -0
- package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
- package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
- package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +64 -0
- package/apps/storefront/src/pages/api/csp-report.ts +25 -0
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
- package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
- package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +44 -0
- package/apps/storefront/src/pages/api/newsletter.ts +104 -0
- package/apps/storefront/src/pages/api/request-access.ts +172 -0
- package/apps/storefront/src/pages/auth/login.astro +149 -0
- package/apps/storefront/src/pages/auth/magic.astro +105 -0
- package/apps/storefront/src/pages/capabilities.astro +292 -0
- package/apps/storefront/src/pages/collections/[handle].astro +154 -0
- package/apps/storefront/src/pages/collections/index.astro +59 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
- package/apps/storefront/src/pages/dashboard/index.astro +160 -0
- package/apps/storefront/src/pages/dev.astro +653 -0
- package/apps/storefront/src/pages/index.astro +290 -0
- package/apps/storefront/src/pages/llms.txt.ts +92 -0
- package/apps/storefront/src/pages/products/[handle].astro +443 -0
- package/apps/storefront/src/pages/robots.txt.ts +24 -0
- package/apps/storefront/src/pages/saved.astro +35 -0
- package/apps/storefront/src/pages/search.astro +150 -0
- package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +646 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +85 -0
- package/apps/storefront/src/pages/style-guide/index.astro +94 -0
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
- package/apps/storefront/src/styles/global.css +277 -0
- package/apps/storefront/src/themes/reference.ts +61 -0
- package/apps/storefront/src/themes/schema.ts +111 -0
- package/apps/storefront/src/themes/tenants/index.ts +51 -0
- package/apps/storefront/tsconfig.json +16 -0
- package/apps/storefront/vitest.config.ts +22 -0
- package/apps/storefront/wrangler.toml +175 -0
- package/package.json +23 -0
- package/packages/public-runtime/LICENSE +58 -0
- package/packages/public-runtime/package.json +22 -0
- package/packages/public-runtime/src/binary-path.ts +68 -0
- package/packages/public-runtime/src/capabilities.ts +89 -0
- package/packages/public-runtime/src/catalog-api.ts +81 -0
- package/packages/public-runtime/src/catalog-d1.ts +192 -0
- package/packages/public-runtime/src/csp.ts +109 -0
- package/packages/public-runtime/src/customization-preview.ts +197 -0
- package/packages/public-runtime/src/customization-reconcile.ts +63 -0
- package/packages/public-runtime/src/customization-runtime.ts +157 -0
- package/packages/public-runtime/src/customization-scripts.ts +64 -0
- package/packages/public-runtime/src/customization-versioning.ts +95 -0
- package/packages/public-runtime/src/foxy.ts +184 -0
- package/packages/public-runtime/src/index.ts +30 -0
- package/packages/public-runtime/src/product.ts +159 -0
- package/packages/public-runtime/src/search.ts +45 -0
- package/packages/public-runtime/src/tenant-assets.ts +444 -0
- package/packages/public-runtime/src/tenant.ts +243 -0
- package/packages/public-runtime/tsconfig.json +8 -0
- package/packages/public-runtime/vitest.config.ts +8 -0
- package/pnpm-workspace.yaml +7 -0
- package/scripts/build/copy-tenant-assets.mjs +69 -0
- package/scripts/tot-dev.mjs +439 -0
- package/tenants/bigdvapor/content/blog.json +52 -0
- package/tenants/bigdvapor/content/chrome.html +149 -0
- package/tenants/bigdvapor/content/chrome.json +47 -0
- package/tenants/bigdvapor/content/home.html +463 -0
- package/tenants/bigdvapor/content/home.json +73 -0
- package/tenants/bigdvapor/content/pages/about.json +5 -0
- package/tenants/bigdvapor/content/pages/privacy.json +6 -0
- package/tenants/bigdvapor/content/pages/shipping-returns.json +6 -0
- package/tenants/bigdvapor/content/pages-html/blogs/news.html +86 -0
- package/tenants/bigdvapor/content/pages-html/pages/about-us.html +106 -0
- package/tenants/bigdvapor/content/pages-html/pages/contact-us.html +61 -0
- package/tenants/bigdvapor/content/pages-html/pages/privacy-policy.html +72 -0
- package/tenants/bigdvapor/content/pages-html/pages/shipping-returns.html +172 -0
- package/tenants/bigdvapor/content/themes/bigd-navy.json +109 -0
- package/tenants/bigdvapor/public/img/brands/adjust-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/again-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/al-fakher-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/arro-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/cookies-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/core-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/cyclone-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/dinner-lady-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/extre-bar-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/fifty-bar-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/firerose-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/flavor-beast-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/foger-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/kk-energy-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/kumi-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/lost-vape-dispoable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/melo-labs-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/mixo-vape-disposable.webp +0 -0
- package/tenants/bigdvapor/public/img/brands/one-tank-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/oxbar-vape-disposable.webp +0 -0
- package/tenants/bigdvapor/public/img/brands/pillow-talk-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/pod-king-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/raz-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/hero-texas.jpg +0 -0
- package/tenants/bigdvapor/public/img/og.jpg +0 -0
- package/tenants/bigdvapor/public/img/pages/AdobeStock_202106312_web.jpg +0 -0
- package/tenants/bigdvapor/public/img/pages/CREW_web.jpg +0 -0
- package/tenants/bigdvapor/public/img/pages/Hero-Image.jpg +0 -0
- package/tenants/bigdvapor/public/img/tiles/adjust.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/again.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/al-fakher.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/arro.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/cookies.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/core.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/cyclone.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/disposables.svg +23 -0
- package/tenants/bigdvapor/public/logo-wordmark.png +0 -0
- package/tenants/bigdvapor/public/pages/home.css +118 -0
- package/tenants/bigdvapor/public/pages/mkt.css +185 -0
- package/tenants/bigdvapor/public/pages/page.css +148 -0
- package/tenants/bigdvapor/public/themes/bigd-navy.css +73 -0
- package/tenants/bigdvapor/theme.json +12 -0
- package/tenants/giantvapes/content/chrome.html +152 -0
- package/tenants/giantvapes/content/chrome.json +94 -0
- package/tenants/giantvapes/content/home.html +194 -0
- package/tenants/giantvapes/content/home.json +50 -0
- package/tenants/giantvapes/content/pages/about.json +10 -0
- package/tenants/giantvapes/content/pages/privacy.json +6 -0
- package/tenants/giantvapes/content/pages/shipping-returns.json +6 -0
- package/tenants/giantvapes/content/pages-html/blogs/news.html +68 -0
- package/tenants/giantvapes/content/pages-html/pages/about-us.html +95 -0
- package/tenants/giantvapes/content/pages-html/pages/contact-us.html +68 -0
- package/tenants/giantvapes/content/pages-html/pages/privacy-policy.html +65 -0
- package/tenants/giantvapes/content/pages-html/pages/shipping-returns.html +77 -0
- package/tenants/giantvapes/content/themes/giant-navy.json +73 -0
- package/tenants/giantvapes/public/img/hero-suicide-bunny.jpg +0 -0
- package/tenants/giantvapes/public/img/hero.webp +0 -0
- package/tenants/giantvapes/public/img/og.jpg +0 -0
- package/tenants/giantvapes/public/logo-wordmark.png +0 -0
- package/tenants/giantvapes/public/pages/home.css +120 -0
- package/tenants/giantvapes/public/pages/mkt.css +185 -0
- package/tenants/giantvapes/public/pages/page.css +155 -0
- package/tenants/giantvapes/public/themes/giant-navy.css +76 -0
- package/tenants/giantvapes/theme.json +39 -0
- package/tenants/home/content/chrome.json +11 -0
- package/tenants/home/content/home.html +304 -0
- package/tenants/home/content/home.json +13 -0
- package/tenants/home/public/js/storefront.js +210 -0
- package/tenants/home/public/logo-wordmark.png +0 -0
- package/tenants/home/public/pages/storefront.css +212 -0
- package/tenants/home/theme.json +6 -0
- package/tenants/tokenoftrust/.tot/config.json +9 -0
- package/tenants/tokenoftrust/content/chrome.json +55 -0
- package/tenants/tokenoftrust/content/home.html +723 -0
- package/tenants/tokenoftrust/content/home.json +233 -0
- package/tenants/tokenoftrust/content/pages-html/careers.html +231 -0
- package/tenants/tokenoftrust/content/pages-html/company.html +275 -0
- package/tenants/tokenoftrust/content/pages-html/contact/contact-sales.html +188 -0
- package/tenants/tokenoftrust/content/pages-html/contact/startup-apply.html +206 -0
- package/tenants/tokenoftrust/content/pages-html/contact.html +178 -0
- package/tenants/tokenoftrust/content/pages-html/industries/adult-content-age-verification.html +286 -0
- package/tenants/tokenoftrust/content/pages-html/industries/cannabis.html +277 -0
- package/tenants/tokenoftrust/content/pages-html/pact-act-pricing.html +289 -0
- package/tenants/tokenoftrust/content/pages-html/pricing.html +483 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-estimation.html +278 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-gate.html +279 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-verification.html +265 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance.html +173 -0
- package/tenants/tokenoftrust/content/pages-html/product/compliance/aml.html +214 -0
- package/tenants/tokenoftrust/content/pages-html/product/compliance/pact-act.html +226 -0
- package/tenants/tokenoftrust/content/pages-html/product/fraud-prevention.html +254 -0
- package/tenants/tokenoftrust/content/pages-html/product/social-profile-verification.html +212 -0
- package/tenants/tokenoftrust/content/pages-html/product/tax/excise-tax.html +213 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/aml.html +259 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/biometric-selfie.html +305 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/coverage.html +254 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/document-verification.html +306 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/electronic-identity-verification.html +304 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/government-id-verification.html +308 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification.html +183 -0
- package/tenants/tokenoftrust/content/pages-html/product.html +228 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations/bigcommerce-integration.html +220 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations/shopify-integration.html +221 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations/wordpress.html +220 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations.html +172 -0
- package/tenants/tokenoftrust/content/pages-html/resources/refer-a-friend-program.html +317 -0
- package/tenants/tokenoftrust/content/pages-html/resources.html +177 -0
- package/tenants/tokenoftrust/content/pages-html/reviews.html +273 -0
- package/tenants/tokenoftrust/content/pages-html/solutions.html +292 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-alcohol.html +289 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-cigars-and-tobacco.html +286 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-firearm-accessories.html +267 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-peptides.html +273 -0
- package/tenants/tokenoftrust/content/themes/tot-navy.json +88 -0
- package/tenants/tokenoftrust/public/favicon.png +0 -0
- package/tenants/tokenoftrust/public/logo-icon.png +0 -0
- package/tenants/tokenoftrust/public/logo-wordmark.png +0 -0
- package/tenants/tokenoftrust/public/pages/company.css +108 -0
- package/tenants/tokenoftrust/public/pages/home.css +457 -0
- package/tenants/tokenoftrust/public/pages/mkt.css +482 -0
- package/tenants/tokenoftrust/public/pages/pricing.css +214 -0
- package/tenants/tokenoftrust/public/pages/solutions.css +66 -0
- package/tenants/tokenoftrust/public/themes/tot-navy-1.0.0.css +136 -0
- package/tenants/tokenoftrust/public/themes/tot-navy.css +358 -0
- package/tenants/tokenoftrust/theme.json +31 -0
- package/tsconfig.base.json +20 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D1 catalog read-model queries (the one-store target — epics/edge-read-replica.md).
|
|
3
|
+
*
|
|
4
|
+
* Queries run against a small `Queryable` seam so the SAME code serves D1 in the
|
|
5
|
+
* Worker (via `fromD1`) and an in-memory `node:sqlite` in tests. Covers FTS5
|
|
6
|
+
* (trigram) search, getProductByHandle, collections, and faceted `listProducts`
|
|
7
|
+
* (filter + sort + paginate + facet counts in SQL GROUP BY) — the full read path,
|
|
8
|
+
* verified end-to-end against the real D1 engine (workerd).
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
CATALOG_D1_SCHEMA,
|
|
12
|
+
toProductRow,
|
|
13
|
+
toCollectionRow,
|
|
14
|
+
facetRows,
|
|
15
|
+
collectionRows,
|
|
16
|
+
type CatalogCollection,
|
|
17
|
+
type CatalogProduct,
|
|
18
|
+
type Facet,
|
|
19
|
+
type FacetValue,
|
|
20
|
+
type ListProductsQuery,
|
|
21
|
+
type ListProductsResponse,
|
|
22
|
+
} from "@tot/public-runtime";
|
|
23
|
+
|
|
24
|
+
/** Minimal async query seam — implemented by D1 and by the node:sqlite test shim. */
|
|
25
|
+
export interface Queryable {
|
|
26
|
+
all<T = Record<string, unknown>>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
27
|
+
first<T = Record<string, unknown>>(sql: string, ...params: unknown[]): Promise<T | null>;
|
|
28
|
+
run(sql: string, ...params: unknown[]): Promise<void>;
|
|
29
|
+
exec(sql: string): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The slice of the Cloudflare D1Database we use (kept local to avoid a types dep). */
|
|
33
|
+
export interface D1Like {
|
|
34
|
+
prepare(sql: string): {
|
|
35
|
+
bind(...vals: unknown[]): {
|
|
36
|
+
all(): Promise<{ results: unknown[] }>;
|
|
37
|
+
first(): Promise<unknown>;
|
|
38
|
+
run(): Promise<unknown>;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
exec(sql: string): Promise<unknown>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Adapt a Cloudflare D1Database to the Queryable seam. */
|
|
45
|
+
export function fromD1(db: D1Like): Queryable {
|
|
46
|
+
return {
|
|
47
|
+
all: async <T>(sql: string, ...params: unknown[]) =>
|
|
48
|
+
(await db.prepare(sql).bind(...params).all()).results as T[],
|
|
49
|
+
first: async <T>(sql: string, ...params: unknown[]) =>
|
|
50
|
+
((await db.prepare(sql).bind(...params).first()) ?? null) as T | null,
|
|
51
|
+
run: async (sql: string, ...params: unknown[]) => {
|
|
52
|
+
await db.prepare(sql).bind(...params).run();
|
|
53
|
+
},
|
|
54
|
+
exec: async (sql: string) => {
|
|
55
|
+
await db.exec(sql);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Create the catalog schema (idempotent). Used for local/test seeding. */
|
|
61
|
+
export async function initSchema(db: Queryable): Promise<void> {
|
|
62
|
+
await db.exec(CATALOG_D1_SCHEMA);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Insert one product into all read-model tables (products, fts, facets, collections). */
|
|
66
|
+
export async function insertProduct(db: Queryable, p: CatalogProduct, position = 0): Promise<void> {
|
|
67
|
+
const r = toProductRow(p, position);
|
|
68
|
+
await db.run(
|
|
69
|
+
`INSERT OR REPLACE INTO products
|
|
70
|
+
(handle,title,vendor,product_type,tags,description_text,price_min,price_max,currency,image,available,status,position,created_at,data)
|
|
71
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
72
|
+
r.handle, r.title, r.vendor, r.product_type, r.tags, r.description_text,
|
|
73
|
+
r.price_min, r.price_max, r.currency, r.image, r.available, r.status,
|
|
74
|
+
r.position, r.created_at, r.data,
|
|
75
|
+
);
|
|
76
|
+
await db.run(
|
|
77
|
+
`INSERT INTO products_fts (handle,title,vendor,product_type,tags,description_text)
|
|
78
|
+
VALUES (?,?,?,?,?,?)`,
|
|
79
|
+
p.handle, p.title, p.vendor ?? "", p.product_type ?? "", p.tags.join(" "), p.description_text,
|
|
80
|
+
);
|
|
81
|
+
for (const f of facetRows(p)) {
|
|
82
|
+
await db.run(`INSERT INTO product_facets (handle,name,value) VALUES (?,?,?)`, p.handle, f.name, f.value);
|
|
83
|
+
}
|
|
84
|
+
for (const c of collectionRows(p)) {
|
|
85
|
+
await db.run(`INSERT INTO product_collections (handle,collection) VALUES (?,?)`, p.handle, c.collection);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Insert one collection row. */
|
|
90
|
+
export async function insertCollection(db: Queryable, c: CatalogCollection): Promise<void> {
|
|
91
|
+
const r = toCollectionRow(c);
|
|
92
|
+
await db.run(
|
|
93
|
+
`INSERT OR REPLACE INTO collections (handle,title,position,data) VALUES (?,?,?,?)`,
|
|
94
|
+
r.handle, r.title, r.position, r.data,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function listCollections(db: Queryable): Promise<CatalogCollection[]> {
|
|
99
|
+
const rows = await db.all<{ data: string }>(`SELECT data FROM collections ORDER BY position ASC`);
|
|
100
|
+
return rows.map((r) => JSON.parse(r.data) as CatalogCollection);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function getCollectionByHandle(
|
|
104
|
+
db: Queryable,
|
|
105
|
+
handle: string,
|
|
106
|
+
): Promise<CatalogCollection | null> {
|
|
107
|
+
const row = await db.first<{ data: string }>(
|
|
108
|
+
`SELECT data FROM collections WHERE handle = ?`,
|
|
109
|
+
handle,
|
|
110
|
+
);
|
|
111
|
+
return row ? (JSON.parse(row.data) as CatalogCollection) : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Build an FTS5 query expression: implicit-AND of quoted terms (trigram substring). */
|
|
115
|
+
function ftsExpr(q: string): string | null {
|
|
116
|
+
const terms = q
|
|
117
|
+
.trim()
|
|
118
|
+
.split(/\s+/)
|
|
119
|
+
.filter((t) => t.length >= 3) // FTS5 trigram tokens are 3 chars; shorter can't match
|
|
120
|
+
.map((t) => `"${t.replace(/"/g, '""')}"`);
|
|
121
|
+
return terms.length ? terms.join(" ") : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Typo-tolerant search over the active catalog, ranked by FTS5 relevance. */
|
|
125
|
+
export async function searchProducts(
|
|
126
|
+
db: Queryable,
|
|
127
|
+
q: string,
|
|
128
|
+
limit = 24,
|
|
129
|
+
): Promise<CatalogProduct[]> {
|
|
130
|
+
const expr = ftsExpr(q);
|
|
131
|
+
if (!expr) return [];
|
|
132
|
+
const rows = await db.all<{ data: string }>(
|
|
133
|
+
`SELECT p.data AS data
|
|
134
|
+
FROM products_fts f
|
|
135
|
+
JOIN products p ON p.handle = f.handle
|
|
136
|
+
WHERE products_fts MATCH ? AND p.status = 'active'
|
|
137
|
+
ORDER BY rank
|
|
138
|
+
LIMIT ?`,
|
|
139
|
+
expr, limit,
|
|
140
|
+
);
|
|
141
|
+
return rows.map((r) => JSON.parse(r.data) as CatalogProduct);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Full product by handle (resolves drafts too — callers gate visibility). */
|
|
145
|
+
export async function getProductByHandle(
|
|
146
|
+
db: Queryable,
|
|
147
|
+
handle: string,
|
|
148
|
+
): Promise<CatalogProduct | null> {
|
|
149
|
+
const row = await db.first<{ data: string }>(
|
|
150
|
+
`SELECT data FROM products WHERE handle = ?`,
|
|
151
|
+
handle,
|
|
152
|
+
);
|
|
153
|
+
return row ? (JSON.parse(row.data) as CatalogProduct) : null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sortClause(sort?: ListProductsQuery["sort"]): string {
|
|
157
|
+
switch (sort) {
|
|
158
|
+
case "price-asc":
|
|
159
|
+
return "ORDER BY p.price_min ASC, p.title COLLATE NOCASE ASC";
|
|
160
|
+
case "price-desc":
|
|
161
|
+
return "ORDER BY p.price_min DESC, p.title COLLATE NOCASE ASC";
|
|
162
|
+
case "newest":
|
|
163
|
+
return "ORDER BY p.created_at DESC";
|
|
164
|
+
case "title-asc":
|
|
165
|
+
return "ORDER BY p.title COLLATE NOCASE ASC";
|
|
166
|
+
case "featured":
|
|
167
|
+
default:
|
|
168
|
+
// Bestsellers float up, then the load order (position) — mirrors the
|
|
169
|
+
// in-Worker featured sort.
|
|
170
|
+
return `ORDER BY (CASE WHEN EXISTS (
|
|
171
|
+
SELECT 1 FROM product_facets f
|
|
172
|
+
WHERE f.handle = p.handle AND f.name = 'tags' AND f.value = 'bestseller'
|
|
173
|
+
) THEN 0 ELSE 1 END), p.position ASC`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Facet counts over the collection-scoped pool (BEFORE option/tag/price filters). */
|
|
178
|
+
async function buildFacetsSql(
|
|
179
|
+
db: Queryable,
|
|
180
|
+
poolSql: string,
|
|
181
|
+
poolParams: unknown[],
|
|
182
|
+
): Promise<Facet[]> {
|
|
183
|
+
const rows = await db.all<{ name: string; value: string; n: number }>(
|
|
184
|
+
`SELECT pf.name AS name, pf.value AS value, COUNT(DISTINCT pf.handle) AS n
|
|
185
|
+
FROM product_facets pf
|
|
186
|
+
WHERE pf.handle IN (SELECT p.handle ${poolSql})
|
|
187
|
+
GROUP BY pf.name, pf.value
|
|
188
|
+
ORDER BY pf.name ASC, n DESC, pf.value ASC`,
|
|
189
|
+
...poolParams,
|
|
190
|
+
);
|
|
191
|
+
const byName = new Map<string, FacetValue[]>();
|
|
192
|
+
for (const r of rows) {
|
|
193
|
+
const values = byName.get(r.name) ?? [];
|
|
194
|
+
values.push({ value: r.value, count: r.n });
|
|
195
|
+
byName.set(r.name, values);
|
|
196
|
+
}
|
|
197
|
+
const facets: Facet[] = [...byName.entries()].map(([name, values]) => ({ name, values }));
|
|
198
|
+
|
|
199
|
+
const avail = await db.first<{ instock: number | null; total: number }>(
|
|
200
|
+
`SELECT SUM(CASE WHEN p.available = 1 THEN 1 ELSE 0 END) AS instock, COUNT(*) AS total ${poolSql}`,
|
|
201
|
+
...poolParams,
|
|
202
|
+
);
|
|
203
|
+
facets.push({
|
|
204
|
+
name: "availability",
|
|
205
|
+
values: [
|
|
206
|
+
{ value: "in-stock", count: avail?.instock ?? 0 },
|
|
207
|
+
{ value: "all", count: avail?.total ?? 0 },
|
|
208
|
+
],
|
|
209
|
+
});
|
|
210
|
+
return facets;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Faceted listing — filter, sort, paginate, and count facets in SQL, replacing
|
|
215
|
+
* the in-Worker O(products×options×variants) scan. Option/tag filters are
|
|
216
|
+
* intra-group OR, inter-group AND (a product matches a group if it has any
|
|
217
|
+
* variant value in it). Facets are computed over the collection-scoped pool,
|
|
218
|
+
* BEFORE the option/tag/price filters, to match the storefront's facet UI.
|
|
219
|
+
*/
|
|
220
|
+
export async function listProducts(
|
|
221
|
+
db: Queryable,
|
|
222
|
+
query: ListProductsQuery = {},
|
|
223
|
+
): Promise<ListProductsResponse> {
|
|
224
|
+
const pageSize = Math.max(1, query.pageSize ?? 12);
|
|
225
|
+
|
|
226
|
+
// Pool: active products, optionally scoped to a collection.
|
|
227
|
+
const poolJoin = query.collection
|
|
228
|
+
? "JOIN product_collections pc ON pc.handle = p.handle AND pc.collection = ?"
|
|
229
|
+
: "";
|
|
230
|
+
const poolParams: unknown[] = query.collection ? [query.collection] : [];
|
|
231
|
+
const poolSql = `FROM products p ${poolJoin} WHERE p.status = 'active'`;
|
|
232
|
+
|
|
233
|
+
// Result filters layered on the pool.
|
|
234
|
+
const filterSql: string[] = [];
|
|
235
|
+
const filterParams: unknown[] = [];
|
|
236
|
+
|
|
237
|
+
for (const [name, values] of Object.entries(query.filters ?? {})) {
|
|
238
|
+
if (name === "availability" || name === "price" || !values.length) continue;
|
|
239
|
+
const placeholders = values.map(() => "?").join(",");
|
|
240
|
+
filterSql.push(
|
|
241
|
+
`p.handle IN (SELECT handle FROM product_facets WHERE name = ? AND value IN (${placeholders}))`,
|
|
242
|
+
);
|
|
243
|
+
filterParams.push(name, ...values);
|
|
244
|
+
}
|
|
245
|
+
if (query.tags?.length) {
|
|
246
|
+
const placeholders = query.tags.map(() => "?").join(",");
|
|
247
|
+
filterSql.push(
|
|
248
|
+
`p.handle IN (SELECT handle FROM product_facets WHERE name = 'tags' AND value IN (${placeholders}))`,
|
|
249
|
+
);
|
|
250
|
+
filterParams.push(...query.tags);
|
|
251
|
+
}
|
|
252
|
+
if (query.availability === "in-stock") filterSql.push("p.available = 1");
|
|
253
|
+
if (query.priceMin != null) {
|
|
254
|
+
filterSql.push("p.price_max >= ?");
|
|
255
|
+
filterParams.push(query.priceMin);
|
|
256
|
+
}
|
|
257
|
+
if (query.priceMax != null) {
|
|
258
|
+
filterSql.push("p.price_min <= ?");
|
|
259
|
+
filterParams.push(query.priceMax);
|
|
260
|
+
}
|
|
261
|
+
if (query.q?.trim()) {
|
|
262
|
+
const like = `%${query.q.trim().toLowerCase()}%`;
|
|
263
|
+
filterSql.push(
|
|
264
|
+
"(lower(p.title) LIKE ? OR lower(p.vendor) LIKE ? OR lower(p.product_type) LIKE ? OR lower(p.description_text) LIKE ? OR lower(p.tags) LIKE ?)",
|
|
265
|
+
);
|
|
266
|
+
filterParams.push(like, like, like, like, like);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const filterClause = filterSql.length ? ` AND ${filterSql.join(" AND ")}` : "";
|
|
270
|
+
const resultParams = [...poolParams, ...filterParams];
|
|
271
|
+
|
|
272
|
+
const totalRow = await db.first<{ n: number }>(
|
|
273
|
+
`SELECT COUNT(*) AS n ${poolSql}${filterClause}`,
|
|
274
|
+
...resultParams,
|
|
275
|
+
);
|
|
276
|
+
const total = totalRow?.n ?? 0;
|
|
277
|
+
const pageCount = Math.max(1, Math.ceil(total / pageSize));
|
|
278
|
+
const page = Math.min(Math.max(1, query.page ?? 1), pageCount);
|
|
279
|
+
const offset = (page - 1) * pageSize;
|
|
280
|
+
|
|
281
|
+
const rows = await db.all<{ data: string }>(
|
|
282
|
+
`SELECT p.data AS data ${poolSql}${filterClause} ${sortClause(query.sort)} LIMIT ? OFFSET ?`,
|
|
283
|
+
...resultParams, pageSize, offset,
|
|
284
|
+
);
|
|
285
|
+
const products = rows.map((r) => JSON.parse(r.data) as CatalogProduct);
|
|
286
|
+
|
|
287
|
+
const facets = await buildFacetsSql(db, poolSql, poolParams);
|
|
288
|
+
return { products, page, pageSize, total, facets };
|
|
289
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the ToT-staff two-phase admission (#8425 staff access), kept
|
|
3
|
+
* out of the Astro route files so the gate + selection-builder are unit-testable.
|
|
4
|
+
*
|
|
5
|
+
* PHASE 1 (admission gate): only a session that actually carries ToT-staff scope
|
|
6
|
+
* (`staff[]` non-empty) may search vendors or enter one. This is the near-side
|
|
7
|
+
* gate on the storefront's staff endpoints; tot20 re-checks staff scope + writes
|
|
8
|
+
* the audit row on the far side (`staffVendorAccess`), so this is defence-in-depth,
|
|
9
|
+
* not the authority.
|
|
10
|
+
*
|
|
11
|
+
* PHASE 2 (scope): after tot20 admits, we build a short-lived {@link StaffSelection}
|
|
12
|
+
* scoped to the returned staffRoles capability. It is stamped on the session as a
|
|
13
|
+
* DISTINCT marker from `tenants[]` — never a synthesized membership.
|
|
14
|
+
*/
|
|
15
|
+
import type { SessionRecord, StaffSelection } from "@/lib/auth/session";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Staff selection lifetime. Deliberately short: staff cross-vendor access is a
|
|
19
|
+
* deliberate, audited act, not a standing grant — re-entering re-audits.
|
|
20
|
+
*/
|
|
21
|
+
export const STAFF_SELECTION_TTL_SECONDS = 30 * 60;
|
|
22
|
+
|
|
23
|
+
/** True when the session carries ToT-staff scope (the admission-gate predicate). */
|
|
24
|
+
export function isStaffSession(
|
|
25
|
+
session: Pick<SessionRecord, "staff"> | null | undefined,
|
|
26
|
+
): boolean {
|
|
27
|
+
return !!session && Array.isArray(session.staff) && session.staff.length > 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Build the short-lived staff selection marker to stamp on the session. */
|
|
31
|
+
export function buildStaffSelection(input: {
|
|
32
|
+
resource: string;
|
|
33
|
+
capability: string;
|
|
34
|
+
nowSeconds: number;
|
|
35
|
+
ttlSeconds?: number;
|
|
36
|
+
}): StaffSelection {
|
|
37
|
+
const ttl = input.ttlSeconds && input.ttlSeconds > 0 ? input.ttlSeconds : STAFF_SELECTION_TTL_SECONDS;
|
|
38
|
+
return {
|
|
39
|
+
resource: input.resource.trim(),
|
|
40
|
+
capability: input.capability,
|
|
41
|
+
exp: input.nowSeconds + ttl,
|
|
42
|
+
};
|
|
43
|
+
}
|