@tokenoftrust/storefront-runner 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +58 -0
- package/README.md +42 -0
- package/apps/storefront/astro.config.mjs +66 -0
- package/apps/storefront/dev-plugins/dev-loop-state.mjs +147 -0
- package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +238 -0
- package/apps/storefront/env.d.ts +162 -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 +42 -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/public/shared/commerce-chrome.css +369 -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 +55 -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/admin/AdminAIWorkflowsTab.astro +531 -0
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +597 -0
- package/apps/storefront/src/components/admin/AdminFoxyCartSettingsPanel.astro +227 -0
- package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +465 -0
- package/apps/storefront/src/components/admin/AdminProductsTab.astro +901 -0
- package/apps/storefront/src/components/admin/AdminReportingTab.astro +435 -0
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +782 -0
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +1223 -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 +201 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +201 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +234 -0
- package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
- package/apps/storefront/src/components/commerce/ArticleCard.astro +242 -0
- package/apps/storefront/src/components/commerce/BrandGrid.astro +175 -0
- package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
- package/apps/storefront/src/components/commerce/FoxySidecartStyles.astro +61 -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/commerce/ReviewList.astro +54 -0
- package/apps/storefront/src/components/commerce/ReviewSummary.astro +44 -0
- package/apps/storefront/src/components/commerce/TagFilterCloud.astro +149 -0
- package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +37 -0
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +99 -0
- package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +45 -0
- package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
- package/apps/storefront/src/components/compliance/PactActNotice.astro +35 -0
- package/apps/storefront/src/components/compliance/Prop65Warning.astro +80 -0
- package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +38 -0
- package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +35 -0
- package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +53 -0
- package/apps/storefront/src/components/home/Hero.astro +164 -0
- package/apps/storefront/src/components/islands/AgeGate.tsx +109 -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 +375 -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/membership/IllustrativeLabel.astro +56 -0
- package/apps/storefront/src/components/membership/TierCards.astro +164 -0
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +148 -0
- package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
- package/apps/storefront/src/components/plp/FacetSidebar.astro +199 -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/components/style-guide/StyleGuideNav.astro +95 -0
- package/apps/storefront/src/components/subscription/HowItWorksSteps.astro +67 -0
- package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +67 -0
- package/apps/storefront/src/components/subscription/SavingsExplainer.astro +101 -0
- package/apps/storefront/src/config/adminTargetTenant.ts +28 -0
- package/apps/storefront/src/config/devTenantSeed.ts +78 -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 +102 -0
- package/apps/storefront/src/config/tenants.ts +87 -0
- package/apps/storefront/src/layouts/Layout.astro +368 -0
- package/apps/storefront/src/lib/admin/README.md +122 -0
- package/apps/storefront/src/lib/admin/ai/approvalMode.ts +21 -0
- package/apps/storefront/src/lib/admin/ai/askEnvelope.ts +48 -0
- package/apps/storefront/src/lib/admin/ai/audit.ts +14 -0
- package/apps/storefront/src/lib/admin/ai/index.ts +6 -0
- package/apps/storefront/src/lib/admin/ai/operationalRuns.ts +155 -0
- package/apps/storefront/src/lib/admin/ai/recommendation.ts +20 -0
- package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +172 -0
- package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +65 -0
- package/apps/storefront/src/lib/admin/envelope.ts +10 -0
- package/apps/storefront/src/lib/admin/evidence/evidenceReference.ts +32 -0
- package/apps/storefront/src/lib/admin/memoizeByEnvelope.ts +27 -0
- package/apps/storefront/src/lib/admin/orderClient.ts +146 -0
- package/apps/storefront/src/lib/admin/seed.ts +54 -0
- package/apps/storefront/src/lib/admin/services/mock/aiWorkflows.ts +59 -0
- package/apps/storefront/src/lib/admin/services/mock/customers.ts +86 -0
- package/apps/storefront/src/lib/admin/services/mock/evidence.ts +99 -0
- package/apps/storefront/src/lib/admin/services/mock/foxyCartSettings.ts +78 -0
- package/apps/storefront/src/lib/admin/services/mock/fulfillment.ts +62 -0
- package/apps/storefront/src/lib/admin/services/mock/index.ts +12 -0
- package/apps/storefront/src/lib/admin/services/mock/orders.ts +184 -0
- package/apps/storefront/src/lib/admin/services/mock/products.ts +105 -0
- package/apps/storefront/src/lib/admin/services/mock/reporting.ts +51 -0
- package/apps/storefront/src/lib/admin/services/mock/settings.ts +70 -0
- package/apps/storefront/src/lib/admin/services/mock/tenants.ts +29 -0
- package/apps/storefront/src/lib/admin/services/orders.ts +300 -0
- package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +67 -0
- package/apps/storefront/src/lib/admin/viewmodels/customers.ts +56 -0
- package/apps/storefront/src/lib/admin/viewmodels/fulfillment.ts +49 -0
- package/apps/storefront/src/lib/admin/viewmodels/orders.ts +92 -0
- package/apps/storefront/src/lib/admin/viewmodels/products.ts +74 -0
- package/apps/storefront/src/lib/admin/viewmodels/reporting.ts +34 -0
- package/apps/storefront/src/lib/admin/viewmodels/settings.ts +64 -0
- package/apps/storefront/src/lib/adminTenantEnvelope.ts +90 -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 +119 -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 +244 -0
- package/apps/storefront/src/lib/chrome/parseConfig.ts +34 -0
- package/apps/storefront/src/lib/colors.ts +97 -0
- package/apps/storefront/src/lib/commerce/foxyCartSettings.ts +253 -0
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +91 -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 +366 -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/activityIngestToken.ts +78 -0
- package/apps/storefront/src/lib/dev/activityStore.ts +72 -0
- package/apps/storefront/src/lib/dev/ai-widget-client.js +308 -0
- package/apps/storefront/src/lib/dev/apiBase.ts +14 -0
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +155 -0
- package/apps/storefront/src/lib/dev/devDraft.ts +133 -0
- package/apps/storefront/src/lib/dev/hostedCockpit.ts +169 -0
- package/apps/storefront/src/lib/dev/livePreview.ts +77 -0
- package/apps/storefront/src/lib/dev/runtimeStore.ts +120 -0
- package/apps/storefront/src/lib/dev/storeStatus.ts +142 -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 +38 -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 +166 -0
- package/apps/storefront/src/lib/maintenance.ts +71 -0
- package/apps/storefront/src/lib/membership/model.ts +97 -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/priceBuckets.ts +33 -0
- package/apps/storefront/src/lib/rawChrome.ts +505 -0
- package/apps/storefront/src/lib/rawMarketingHtml.ts +136 -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 +607 -0
- package/apps/storefront/src/lib/subscription/model.ts +68 -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 +249 -0
- package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
- package/apps/storefront/src/lib/url.ts +142 -0
- package/apps/storefront/src/lib/wishlist.ts +63 -0
- package/apps/storefront/src/middleware/index.ts +455 -0
- package/apps/storefront/src/pages/404.astro +61 -0
- package/apps/storefront/src/pages/[...slug].astro +90 -0
- package/apps/storefront/src/pages/admin.astro +1675 -0
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +53 -0
- package/apps/storefront/src/pages/api/admin/orders.ts +71 -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/checkout-style.css.ts +43 -0
- package/apps/storefront/src/pages/api/cli/policy.ts +70 -0
- package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +164 -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/activity.ts +124 -0
- package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +73 -0
- package/apps/storefront/src/pages/api/dev/diff.ts +68 -0
- package/apps/storefront/src/pages/api/dev/file.ts +57 -0
- package/apps/storefront/src/pages/api/dev/locate.ts +125 -0
- package/apps/storefront/src/pages/api/dev/publish.ts +128 -0
- package/apps/storefront/src/pages/api/dev/tree.ts +70 -0
- package/apps/storefront/src/pages/api/dev/write.ts +116 -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 +177 -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/index.astro +290 -0
- package/apps/storefront/src/pages/llms.txt.ts +92 -0
- package/apps/storefront/src/pages/products/[handle].astro +402 -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 +1280 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +229 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +144 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +116 -0
- package/apps/storefront/src/pages/style-guide/index.astro +98 -0
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
- package/apps/storefront/src/styles/global.css +292 -0
- package/apps/storefront/src/themes/reference.ts +68 -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/package.json +26 -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/checkout-style-publish.ts +200 -0
- package/packages/public-runtime/src/checkout-style.ts +375 -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-orders.ts +136 -0
- package/packages/public-runtime/src/foxy.ts +196 -0
- package/packages/public-runtime/src/hot-standby-readiness.ts +140 -0
- package/packages/public-runtime/src/index.ts +34 -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 +384 -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/dev/ai-edit.mjs +183 -0
- package/scripts/dev/checkout-watch.mjs +116 -0
- package/scripts/dev/file-browser.mjs +159 -0
- package/scripts/dev/file-writer.mjs +141 -0
- package/scripts/dev/git-status.mjs +171 -0
- package/scripts/dev/locate-handler.mjs +163 -0
- package/scripts/dev/locate.mjs +112 -0
- package/scripts/dev/port-check.mjs +48 -0
- package/scripts/dev/safe-path.mjs +109 -0
- package/scripts/dev/shot.mjs +46 -0
- package/scripts/dev/tenant-source-root.mjs +76 -0
- package/scripts/dev/unified-diff.mjs +81 -0
- package/scripts/tot-dev.mjs +376 -0
- package/tenants/README.md +14 -0
- package/tenants/home/content/chrome.json +7 -0
- package/tenants/home/content/home.html +58 -0
- package/tenants/home/content/home.json +11 -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/tsconfig.base.json +20 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Token of Trust Source-Available License — Local Development Only
|
|
2
|
+
Version 1.0
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 Token of Trust, Inc. All rights reserved.
|
|
5
|
+
|
|
6
|
+
1. DEFINITIONS
|
|
7
|
+
"Software" means this source code and any accompanying files in this
|
|
8
|
+
repository or distribution, including the storefront renderer runtime, the
|
|
9
|
+
`@tot/public-runtime` package, the storefront-runner distributable, and any
|
|
10
|
+
derivative works.
|
|
11
|
+
"Local Development" means running, building, and modifying the Software on a
|
|
12
|
+
machine you control, for the sole purpose of evaluating it or developing and
|
|
13
|
+
previewing your own tenant content against it. Serving the Software to any
|
|
14
|
+
third party, or over any public or shared network, is NOT Local Development.
|
|
15
|
+
"Token of Trust" means Token of Trust, Inc.
|
|
16
|
+
|
|
17
|
+
2. GRANT (LOCAL DEVELOPMENT ONLY)
|
|
18
|
+
Subject to the terms below, Token of Trust grants you a personal,
|
|
19
|
+
non-exclusive, non-transferable, revocable license to use, copy, build, and
|
|
20
|
+
modify the Software SOLELY for Local Development.
|
|
21
|
+
|
|
22
|
+
3. PERMISSION REQUIRED FOR EVERYTHING ELSE
|
|
23
|
+
Any use of the Software beyond Local Development requires the PRIOR WRITTEN
|
|
24
|
+
PERMISSION of Token of Trust. Without such permission you may NOT, in whole or
|
|
25
|
+
in part:
|
|
26
|
+
(a) deploy, host, serve, or otherwise run the Software in production or for
|
|
27
|
+
any third party, or on any public, staging, or shared/network-accessible
|
|
28
|
+
environment;
|
|
29
|
+
(b) use the Software for any commercial purpose or to provide a service to
|
|
30
|
+
others;
|
|
31
|
+
(c) distribute, publish, sublicense, sell, rent, lease, or otherwise make the
|
|
32
|
+
Software (or a derivative of it) available to any third party; or
|
|
33
|
+
(d) remove or obscure this license or the copyright notices.
|
|
34
|
+
To request permission, contact Token of Trust at legal@tokenoftrust.com.
|
|
35
|
+
|
|
36
|
+
4. NO TRADEMARK LICENSE
|
|
37
|
+
This license grants no rights to the Token of Trust name, logos, or
|
|
38
|
+
trademarks.
|
|
39
|
+
|
|
40
|
+
5. RESERVATION OF RIGHTS
|
|
41
|
+
All rights not expressly granted are reserved by Token of Trust. This is a
|
|
42
|
+
source-available license, not an open-source license; the Software is not
|
|
43
|
+
licensed under any OSI-approved license.
|
|
44
|
+
|
|
45
|
+
6. TERMINATION
|
|
46
|
+
This license terminates automatically if you breach it. On termination you
|
|
47
|
+
must stop using the Software and destroy all copies in your possession.
|
|
48
|
+
|
|
49
|
+
7. DISCLAIMER OF WARRANTY
|
|
50
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
51
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
52
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
53
|
+
|
|
54
|
+
8. LIMITATION OF LIABILITY
|
|
55
|
+
IN NO EVENT SHALL TOKEN OF TRUST BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
|
56
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
|
|
57
|
+
OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
58
|
+
SOFTWARE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tot/public-runtime",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.ts",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.6.3",
|
|
20
|
+
"vitest": "^2.1.8"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary-vs-text path classification — the SINGLE source of truth shared by the
|
|
3
|
+
* write side (seed's `buildSeedFiles`) and the read side (the Gitea read seam).
|
|
4
|
+
*
|
|
5
|
+
* The extraction/preview loop moves a tenant's files through two seams that MUST
|
|
6
|
+
* agree on what is binary, or a file's bytes round-trip differently on each side
|
|
7
|
+
* and binaries corrupt:
|
|
8
|
+
*
|
|
9
|
+
* - WRITE: `customization-seed.ts#buildSeedFiles` base64-encodes binaries when
|
|
10
|
+
* it materializes the colocated subtree into a flat Gitea-repo seed.
|
|
11
|
+
* - READ: `gitea-source.ts#createGiteaHttpSource().readContent` base64-encodes
|
|
12
|
+
* binaries when it reads them back at a commit for the reconcile/assets sink.
|
|
13
|
+
*
|
|
14
|
+
* Keeping the extension set here (and importing it in both) guarantees the two
|
|
15
|
+
* seams classify identically. Runtime-portable (no `node:*`) so it is safe in the
|
|
16
|
+
* renderer, the Worker, the publish pipeline, and unit tests alike.
|
|
17
|
+
*
|
|
18
|
+
* SVG is intentionally TEXT (it is XML the verbatim-serve path can nonce-inject);
|
|
19
|
+
* CSS/JSON/HTML/JS/TXT/XML are text. Everything in the set below is opaque bytes.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* File extensions treated as BINARY (base64-encoded so raw bytes round-trip).
|
|
24
|
+
* Every other extension is emitted/read as UTF-8 text.
|
|
25
|
+
*/
|
|
26
|
+
export const BINARY_EXTENSIONS: ReadonlySet<string> = new Set([
|
|
27
|
+
// raster images
|
|
28
|
+
"png",
|
|
29
|
+
"jpg",
|
|
30
|
+
"jpeg",
|
|
31
|
+
"gif",
|
|
32
|
+
"webp",
|
|
33
|
+
"avif",
|
|
34
|
+
"ico",
|
|
35
|
+
"bmp",
|
|
36
|
+
"tiff",
|
|
37
|
+
"tif",
|
|
38
|
+
// fonts
|
|
39
|
+
"woff",
|
|
40
|
+
"woff2",
|
|
41
|
+
"ttf",
|
|
42
|
+
"otf",
|
|
43
|
+
"eot",
|
|
44
|
+
// media
|
|
45
|
+
"mp4",
|
|
46
|
+
"webm",
|
|
47
|
+
"mov",
|
|
48
|
+
"mp3",
|
|
49
|
+
"wav",
|
|
50
|
+
"ogg",
|
|
51
|
+
// docs / archives
|
|
52
|
+
"pdf",
|
|
53
|
+
"zip",
|
|
54
|
+
"gz",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
/** The lowercased extension of `path` (no leading dot), or "" when it has none. */
|
|
58
|
+
export function extensionOf(path: string): string {
|
|
59
|
+
const dot = path.lastIndexOf(".");
|
|
60
|
+
const slash = path.lastIndexOf("/");
|
|
61
|
+
if (dot === -1 || dot < slash) return "";
|
|
62
|
+
return path.slice(dot + 1).toLowerCase();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** True when `path` names a binary asset (base64), false for text. */
|
|
66
|
+
export function isBinaryPath(path: string): boolean {
|
|
67
|
+
return BINARY_EXTENSIONS.has(extensionOf(path));
|
|
68
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant capability switches — config that travels with the PR (Track A/B).
|
|
3
|
+
*
|
|
4
|
+
* Three independently-toggleable capabilities:
|
|
5
|
+
* - cartCheckout — FoxyCart cart/checkout ("Powered by FoxyCart", Track A)
|
|
6
|
+
* - ageVerification — Token of Trust age/identity gate (21+ regulated)
|
|
7
|
+
* - exciseTax — excise tax collection at checkout (Track B, tot-foxycart)
|
|
8
|
+
*
|
|
9
|
+
* The source of truth is the tenant repo's `.tot/config.json` `capabilities`
|
|
10
|
+
* block — so a change is a code change that travels with a PR. Resolution is
|
|
11
|
+
* per-environment on the SAME published-artifact spine as theme/content:
|
|
12
|
+
* localhost → working-tree config (ungated, HMR)
|
|
13
|
+
* preview → PR-head config reconciled to the `test` pointer
|
|
14
|
+
* live → the promoted config on the `live` pointer (evidence-gated promote)
|
|
15
|
+
* so each environment's capability state is independently pinned and a shared-env
|
|
16
|
+
* change is PR-gated by construction. `readPublishedArtifact(env, capabilities
|
|
17
|
+
* ArtifactPath)` overrides the build-time `TenantConfig.capabilities` default.
|
|
18
|
+
*/
|
|
19
|
+
import type { TenantConfig } from "./tenant.js";
|
|
20
|
+
|
|
21
|
+
export interface CapabilityState {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CapabilityConfig {
|
|
26
|
+
/** FoxyCart cart/checkout: loader + signed add-to-cart. */
|
|
27
|
+
cartCheckout: CapabilityState;
|
|
28
|
+
/** Token of Trust age/identity verification (soft 21+ gate now; full ToT later). */
|
|
29
|
+
ageVerification: CapabilityState;
|
|
30
|
+
/** Excise tax collection at checkout (materialized in the FoxyCart cart). */
|
|
31
|
+
exciseTax: CapabilityState;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type CapabilityKey = keyof CapabilityConfig;
|
|
35
|
+
|
|
36
|
+
/** Stable capability order + labels for any surface that lists them. */
|
|
37
|
+
export const CAPABILITY_KEYS: CapabilityKey[] = [
|
|
38
|
+
"cartCheckout",
|
|
39
|
+
"ageVerification",
|
|
40
|
+
"exciseTax",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Canonical published-artifact path for a tenant's capabilities. Same colocated
|
|
45
|
+
* `tenants/<id>/…` contract as theme/content, so the reconcile pipeline keys the
|
|
46
|
+
* KV artifact by it and the storefront reads it back per environment.
|
|
47
|
+
*/
|
|
48
|
+
export function capabilitiesArtifactPath(tenantId: string): string {
|
|
49
|
+
return `tenants/${tenantId}/capabilities.json`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a tenant's effective capabilities: build-time `TenantConfig.capabilities`
|
|
54
|
+
* default (itself defaulted from siteType/compliance), then any per-environment
|
|
55
|
+
* published `override` layered on top. Undefined entries fall through, so a PR can
|
|
56
|
+
* flip ONE capability without restating the rest.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveCapabilities(
|
|
59
|
+
tenant: Pick<TenantConfig, "capabilities" | "compliance" | "siteType">,
|
|
60
|
+
override?: Partial<CapabilityConfig> | null,
|
|
61
|
+
): CapabilityConfig {
|
|
62
|
+
const c = tenant.capabilities;
|
|
63
|
+
const base: CapabilityConfig = {
|
|
64
|
+
// Commerce tenants get a cart by default; marketing tenants don't.
|
|
65
|
+
cartCheckout: {
|
|
66
|
+
enabled: c?.cartCheckout?.enabled ?? tenant.siteType !== "marketing",
|
|
67
|
+
},
|
|
68
|
+
// Regulated tenants (an age minimum) verify by default.
|
|
69
|
+
ageVerification: {
|
|
70
|
+
enabled: c?.ageVerification?.enabled ?? !!tenant.compliance?.minAge,
|
|
71
|
+
},
|
|
72
|
+
// Nicotine/vape tenants collect excise by default.
|
|
73
|
+
exciseTax: {
|
|
74
|
+
enabled: c?.exciseTax?.enabled ?? !!tenant.compliance?.nicotineWarning,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
if (!override) return base;
|
|
78
|
+
return {
|
|
79
|
+
cartCheckout: {
|
|
80
|
+
enabled: override.cartCheckout?.enabled ?? base.cartCheckout.enabled,
|
|
81
|
+
},
|
|
82
|
+
ageVerification: {
|
|
83
|
+
enabled: override.ageVerification?.enabled ?? base.ageVerification.enabled,
|
|
84
|
+
},
|
|
85
|
+
exciseTax: {
|
|
86
|
+
enabled: override.exciseTax?.enabled ?? base.exciseTax.enabled,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical catalog data contract (shared types). The tot20 HTTP catalog API has
|
|
3
|
+
* been retired — the storefront reads from a Cloudflare D1 edge replica — so these
|
|
4
|
+
* are now the in-process shapes shared across the catalog path:
|
|
5
|
+
* - packages/migration — the LOAD step's bulk-upsert result shape (counts/rows).
|
|
6
|
+
* - apps/storefront — the read clients' (D1 / fixture) list-query, facet,
|
|
7
|
+
* and list-response shapes.
|
|
8
|
+
*
|
|
9
|
+
* Tenant scoping is by `appDomain` (== TenantConfig.tot_data_scope).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { CatalogCollection, CatalogProduct } from "./product.js";
|
|
13
|
+
|
|
14
|
+
/** POST /api/catalog/products:bulkUpsert */
|
|
15
|
+
export interface BulkUpsertProductsRequest {
|
|
16
|
+
appDomain?: string;
|
|
17
|
+
totApiKey?: string;
|
|
18
|
+
totSecretKey?: string;
|
|
19
|
+
products: CatalogProduct[];
|
|
20
|
+
/** When true, server skips records whose source_checksum is unchanged. */
|
|
21
|
+
incremental?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UpsertResultRow {
|
|
25
|
+
source_id: string;
|
|
26
|
+
handle: string;
|
|
27
|
+
action: "inserted" | "updated" | "skipped" | "error";
|
|
28
|
+
message?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface BulkUpsertProductsResponse {
|
|
32
|
+
results: UpsertResultRow[];
|
|
33
|
+
counts: { inserted: number; updated: number; skipped: number; error: number };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** POST /api/catalog/collections:bulkUpsert */
|
|
37
|
+
export interface BulkUpsertCollectionsRequest {
|
|
38
|
+
appDomain?: string;
|
|
39
|
+
totApiKey?: string;
|
|
40
|
+
totSecretKey?: string;
|
|
41
|
+
collections: CatalogCollection[];
|
|
42
|
+
incremental?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BulkUpsertCollectionsResponse {
|
|
46
|
+
results: UpsertResultRow[];
|
|
47
|
+
counts: { inserted: number; updated: number; skipped: number; error: number };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** GET /api/catalog/products (list + facet, querystring-driven) */
|
|
51
|
+
export interface ListProductsQuery {
|
|
52
|
+
collection?: string; // collection handle
|
|
53
|
+
sort?: "featured" | "price-asc" | "price-desc" | "newest" | "title-asc";
|
|
54
|
+
/** Facet filters: option name -> selected values. Encoded as repeated query params. */
|
|
55
|
+
filters?: Record<string, string[]>;
|
|
56
|
+
priceMin?: number;
|
|
57
|
+
priceMax?: number;
|
|
58
|
+
availability?: "in-stock" | "all";
|
|
59
|
+
tags?: string[];
|
|
60
|
+
page?: number;
|
|
61
|
+
pageSize?: number;
|
|
62
|
+
q?: string; // free-text search
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface FacetValue {
|
|
66
|
+
value: string;
|
|
67
|
+
count: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface Facet {
|
|
71
|
+
name: string; // option name or "price"/"availability"/"tags"
|
|
72
|
+
values: FacetValue[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ListProductsResponse {
|
|
76
|
+
products: CatalogProduct[];
|
|
77
|
+
page: number;
|
|
78
|
+
pageSize: number;
|
|
79
|
+
total: number;
|
|
80
|
+
facets: Facet[];
|
|
81
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D1 (edge SQLite) catalog read-model — schema + row projections (the one-store
|
|
3
|
+
* target from epics/edge-read-replica.md). The storefront reads search, faceted
|
|
4
|
+
* listing, and full products from D1; the ETL emits the rows. This is the shared
|
|
5
|
+
* contract both sides agree on.
|
|
6
|
+
*
|
|
7
|
+
* - `products` — one row per product: slim query columns + a `data` blob
|
|
8
|
+
* (the full CatalogProduct JSON) for PDP/listing retrieval.
|
|
9
|
+
* - `product_collections` — membership (handle -> collection handle).
|
|
10
|
+
* - `product_facets` — (handle, name, value) for SQL GROUP BY facet counts.
|
|
11
|
+
* - `products_fts` — FTS5 (trigram) over the searchable text: typo-tolerant
|
|
12
|
+
* search without loading the catalog into the Worker.
|
|
13
|
+
*/
|
|
14
|
+
import type { CatalogCollection, CatalogProduct } from "./product.js";
|
|
15
|
+
|
|
16
|
+
export const CATALOG_D1_SCHEMA = `
|
|
17
|
+
CREATE TABLE IF NOT EXISTS products (
|
|
18
|
+
handle TEXT PRIMARY KEY NOT NULL,
|
|
19
|
+
title TEXT NOT NULL,
|
|
20
|
+
vendor TEXT,
|
|
21
|
+
product_type TEXT,
|
|
22
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
23
|
+
description_text TEXT NOT NULL DEFAULT '',
|
|
24
|
+
price_min REAL NOT NULL,
|
|
25
|
+
price_max REAL NOT NULL,
|
|
26
|
+
currency TEXT NOT NULL,
|
|
27
|
+
image TEXT,
|
|
28
|
+
available INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
status TEXT NOT NULL,
|
|
30
|
+
position INTEGER NOT NULL DEFAULT 0,
|
|
31
|
+
created_at TEXT,
|
|
32
|
+
data TEXT NOT NULL
|
|
33
|
+
);
|
|
34
|
+
CREATE TABLE IF NOT EXISTS product_collections (
|
|
35
|
+
handle TEXT NOT NULL,
|
|
36
|
+
collection TEXT NOT NULL
|
|
37
|
+
);
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_pc_collection ON product_collections (collection);
|
|
39
|
+
CREATE TABLE IF NOT EXISTS product_facets (
|
|
40
|
+
handle TEXT NOT NULL,
|
|
41
|
+
name TEXT NOT NULL,
|
|
42
|
+
value TEXT NOT NULL
|
|
43
|
+
);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_pf_name ON product_facets (name, value);
|
|
45
|
+
CREATE TABLE IF NOT EXISTS collections (
|
|
46
|
+
handle TEXT PRIMARY KEY NOT NULL,
|
|
47
|
+
title TEXT NOT NULL,
|
|
48
|
+
position INTEGER NOT NULL DEFAULT 99,
|
|
49
|
+
data TEXT NOT NULL
|
|
50
|
+
);
|
|
51
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS products_fts USING fts5 (
|
|
52
|
+
handle UNINDEXED, title, vendor, product_type, tags, description_text,
|
|
53
|
+
tokenize = 'trigram'
|
|
54
|
+
);
|
|
55
|
+
`.trim();
|
|
56
|
+
|
|
57
|
+
export interface ProductRow {
|
|
58
|
+
handle: string;
|
|
59
|
+
title: string;
|
|
60
|
+
vendor: string | null;
|
|
61
|
+
product_type: string | null;
|
|
62
|
+
tags: string; // JSON array
|
|
63
|
+
description_text: string;
|
|
64
|
+
price_min: number;
|
|
65
|
+
price_max: number;
|
|
66
|
+
currency: string;
|
|
67
|
+
image: string | null;
|
|
68
|
+
available: number; // 0 | 1
|
|
69
|
+
status: string;
|
|
70
|
+
position: number;
|
|
71
|
+
created_at: string | null;
|
|
72
|
+
data: string; // full CatalogProduct JSON
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const firstImage = (p: CatalogProduct): string | null =>
|
|
76
|
+
(p.images.find((i) => i.url) ?? p.images[0])?.url ?? null;
|
|
77
|
+
|
|
78
|
+
/** Project a CatalogProduct into the `products` row. */
|
|
79
|
+
export function toProductRow(p: CatalogProduct, position = 0): ProductRow {
|
|
80
|
+
return {
|
|
81
|
+
handle: p.handle,
|
|
82
|
+
title: p.title,
|
|
83
|
+
vendor: p.vendor ?? null,
|
|
84
|
+
product_type: p.product_type ?? null,
|
|
85
|
+
tags: JSON.stringify(p.tags),
|
|
86
|
+
description_text: p.description_text,
|
|
87
|
+
price_min: p.price_range.min,
|
|
88
|
+
price_max: p.price_range.max,
|
|
89
|
+
currency: p.price_range.currency,
|
|
90
|
+
image: firstImage(p),
|
|
91
|
+
available: p.variants.some((v) => v.inventory.available) ? 1 : 0,
|
|
92
|
+
status: p.status,
|
|
93
|
+
position,
|
|
94
|
+
created_at: p.created_at,
|
|
95
|
+
data: JSON.stringify(p),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Facet rows: one per distinct option value (across variants) + one per tag. */
|
|
100
|
+
export function facetRows(p: CatalogProduct): Array<{ name: string; value: string }> {
|
|
101
|
+
const out: Array<{ name: string; value: string }> = [];
|
|
102
|
+
for (const opt of p.options) {
|
|
103
|
+
const seen = new Set<string>();
|
|
104
|
+
for (const v of p.variants) {
|
|
105
|
+
const val = v.option_values[opt.name];
|
|
106
|
+
if (val && !seen.has(val)) {
|
|
107
|
+
seen.add(val);
|
|
108
|
+
out.push({ name: opt.name, value: val });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const tag of p.tags) out.push({ name: "tags", value: tag });
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Collection-membership rows for a product. */
|
|
117
|
+
export function collectionRows(p: CatalogProduct): Array<{ collection: string }> {
|
|
118
|
+
return p.collections.map((collection) => ({ collection }));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface CollectionRow {
|
|
122
|
+
handle: string;
|
|
123
|
+
title: string;
|
|
124
|
+
position: number;
|
|
125
|
+
data: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Project a CatalogCollection into the `collections` row. */
|
|
129
|
+
export function toCollectionRow(c: CatalogCollection): CollectionRow {
|
|
130
|
+
return {
|
|
131
|
+
handle: c.handle,
|
|
132
|
+
title: c.title,
|
|
133
|
+
position: c.position ?? 99,
|
|
134
|
+
data: JSON.stringify(c),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** SQL literal: NULL, a number, or a single-quote-escaped string. */
|
|
139
|
+
function lit(v: string | number | null): string {
|
|
140
|
+
if (v === null) return "NULL";
|
|
141
|
+
if (typeof v === "number") return Number.isFinite(v) ? String(v) : "NULL";
|
|
142
|
+
return `'${v.replace(/'/g, "''")}'`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Emit a self-contained SQL seed (schema + a clean reseed of all rows) for a
|
|
147
|
+
* tenant, loadable into D1 via `wrangler d1 execute <DB> --file catalog.sql`.
|
|
148
|
+
* Includes ALL products (status column drives query visibility; getProductByHandle
|
|
149
|
+
* still resolves drafts). Re-runnable: it clears the tables before inserting.
|
|
150
|
+
*/
|
|
151
|
+
export function emitCatalogSql(
|
|
152
|
+
products: CatalogProduct[],
|
|
153
|
+
collections: CatalogCollection[] = [],
|
|
154
|
+
): string {
|
|
155
|
+
const out: string[] = [
|
|
156
|
+
CATALOG_D1_SCHEMA,
|
|
157
|
+
"DELETE FROM products;",
|
|
158
|
+
"DELETE FROM product_collections;",
|
|
159
|
+
"DELETE FROM product_facets;",
|
|
160
|
+
"DELETE FROM products_fts;",
|
|
161
|
+
"DELETE FROM collections;",
|
|
162
|
+
];
|
|
163
|
+
for (const c of collections) {
|
|
164
|
+
const r = toCollectionRow(c);
|
|
165
|
+
out.push(
|
|
166
|
+
`INSERT INTO collections (handle,title,position,data) VALUES (${[r.handle, r.title, r.position, r.data].map(lit).join(",")});`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
let position = 0;
|
|
170
|
+
for (const p of products) {
|
|
171
|
+
const r = toProductRow(p, position++);
|
|
172
|
+
out.push(
|
|
173
|
+
`INSERT INTO products (handle,title,vendor,product_type,tags,description_text,price_min,price_max,currency,image,available,status,position,created_at,data) VALUES (${[
|
|
174
|
+
r.handle, r.title, r.vendor, r.product_type, r.tags, r.description_text,
|
|
175
|
+
r.price_min, r.price_max, r.currency, r.image, r.available, r.status,
|
|
176
|
+
r.position, r.created_at, r.data,
|
|
177
|
+
].map(lit).join(",")});`,
|
|
178
|
+
);
|
|
179
|
+
out.push(
|
|
180
|
+
`INSERT INTO products_fts (handle,title,vendor,product_type,tags,description_text) VALUES (${[
|
|
181
|
+
p.handle, p.title, p.vendor ?? "", p.product_type ?? "", p.tags.join(" "), p.description_text,
|
|
182
|
+
].map(lit).join(",")});`,
|
|
183
|
+
);
|
|
184
|
+
for (const f of facetRows(p)) {
|
|
185
|
+
out.push(`INSERT INTO product_facets (handle,name,value) VALUES (${lit(p.handle)},${lit(f.name)},${lit(f.value)});`);
|
|
186
|
+
}
|
|
187
|
+
for (const c of collectionRows(p)) {
|
|
188
|
+
out.push(`INSERT INTO product_collections (handle,collection) VALUES (${lit(p.handle)},${lit(c.collection)});`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return `${out.join("\n")}\n`;
|
|
192
|
+
}
|