Skip to content

Architecture

Chapter 2. Chapter 1 gave you the ideas; this chapter draws their shape — the four services, the shared packages, and the trust boundary that runs through them.

The shape of the system: four deployed services, a set of shared libraries, and two trust tiers.

The big picture

Three layers on one shared, multi-tenant platform: a public edge (Cloudflare), a private data plane that renders storefronts, and a logged-in control plane (admin + AI). Postgres is the one source of truth; theme bundle bytes live in S3/CloudFront.

At a glance

The two journeys and the shared stores — read this whole:

The complete picture

The full runtime — every service, store, and external dependency, with all the edges. It's dense on purpose; click it (⛶) to read any part full-screen.

  • Read path (shopper → HTML): edge resolves the tenant from KV (DB only on a miss), serves from cache, or proxies to the private origin, which renders from Postgres + S3 + the commerce backend.
  • Write path (merchant/AI → live): admin-web → admin-api (Clerk authN + memberships authZ) → Postgres + S3, then it write-throughs the KV route and purges/warms the edge.
  • The key boundary: the origin cannot be reached from the internet — only the edge can call it, adding the trusted x-ratio-tenant + shared x-edge-auth. The origin never trusts the public Host; and the edge never renders — it always proxies to the ECS origin.

The four services

ServiceRuntimeResponsibilityEntry
apps/edgeCF Workerhost→tenant (KV, DB fallback), edge cache, proxy to origin, strip internal headerssrc/worker.ts
apps/originNode / Hono (ECS)the single renderer: resolve tenant → load theme → resolve data → render → cacheable HTMLsrc/index.ts
apps/admin-apiNode / Hono (ECS)write plane: stores, domains, themes (publish/rollback/rebase), commerce, assistantsrc/server.ts, app.ts
apps/admin-webReact SPA (CF Pages)merchant/admin UI: theme code editor + visual customizer, onboarding, settingssrc/App.tsx

The origin is private — it only answers requests carrying the edge's shared x-edge-auth secret; everything public goes through the edge.

The packages

Grouped by concern (all @ratio/*, under packages/). How the apps sit on the packages, and what talks to the outside:

Rendering & themes

  • builder-core — the storefront domain core: router, page store, ThemeStore (draft/publish/rollback/rebase), theme-compose (base ⊕ overrides), theme-render, the commerce resolver seam, storefront chrome/cart/account.
  • builder-render — the two LiquidJS engines (engine.ts in-process + worker.mjs/isolate.ts isolate), tier inference, first-party sections.
  • builder-registry — the section catalog + tier-enforcement gate + islands.
  • design-tokens — the design-token vocabulary + JSON→CSS generator, shared by the backend and the admin editor (one source of truth, so they can't drift).
  • seo — structured data, <head>, sitemap/robots.

Commerce & storefront add-ons

  • storefront-integrations — the per-store provider seam (cart / login / checkout / account / search) + CSP composition; GoKwik side-cart, KwikPass, checkout live here.

Edge & data

  • edge-core — portable edge logic (tenant resolve, origin proxy + cache, circuit breaker, headers, edge-secret).
  • data-db — Postgres pool, metrics, rate-limit. data-objects — the S3/CDN object store for bundles + assets.
  • data-repoforTenant(id).getTenant(), deny-by-default. data-provisioning — store/domain creation.
  • control-plane-client — a generated typed SDK for the admin API (bearer auth: Clerk or rat_ agent token).

Observability

  • observability (pino/Node), observability-edge (Workers-safe console-JSON), observability-core (pure log conventions + redaction), observability-tracing (OpenTelemetry, off by default).

Two trust tiers (the safety model)

The renderer runs two kinds of code, and the split explains a lot of the design:

  • Trusted, first-party — platform section markup from the registry. Rendered in-process, edge-safe (no Node built-ins), full filter set.
  • Untrusted, merchant/theme Liquid — a merchant's own theme files. Rendered in a worker-thread isolate with a hard wall-clock kill, a curated filter allowlist, and resource limits — so one merchant's runaway template can't starve a co-tenant. See Rendering & themes.

The whole trusted graph is edge-portable; untrusted rendering is origin-only (@ratio/builder-render/isolate is deliberately kept out of the edge-safe barrel).

Repo map

ratio-3.0/
  apps/
    edge/        CF Worker (thin, over edge-core)
    origin/      the renderer (Hono)  — handlers/ = storefront, cart, account, order, search, assets
    admin-api/   write plane (Hono)   — routes/ = stores, domains, themes/*, commerce, assistant
    admin-web/   React SPA            — features/ = theme (editor + customizer), settings, onboarding, admin
    docs/        THIS site (standalone VitePress — excluded from the workspace set)
  packages/      builder-core, builder-render, builder-registry, storefront-integrations,
                 seo, design-tokens, edge-core, data-*, observability-*, control-plane-client
  scripts/       migrate, seed-bases, republish-base, rebase-to-latest-base, onboard, gen-*
  infra/aws/bundle-cdn/   Terraform for the S3 + CloudFront theme-bundle CDN
  db/migrations/ forward-only SQL
  dev/           local dev orchestration (all.ts, server.ts)
  Dockerfile     origin container image

Composition root (ADR-0001)

Only apps/*/src/config.ts read process.env. Packages take injected config — that's why the same builder-core runs in the origin, the admin-api, and (trusted parts) potentially the edge, unchanged.