Appearance
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 +
membershipsauthZ) → 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+ sharedx-edge-auth. The origin never trusts the publicHost; and the edge never renders — it always proxies to the ECS origin.
The four services
| Service | Runtime | Responsibility | Entry |
|---|---|---|---|
apps/edge | CF Worker | host→tenant (KV, DB fallback), edge cache, proxy to origin, strip internal headers | src/worker.ts |
apps/origin | Node / Hono (ECS) | the single renderer: resolve tenant → load theme → resolve data → render → cacheable HTML | src/index.ts |
apps/admin-api | Node / Hono (ECS) | write plane: stores, domains, themes (publish/rollback/rebase), commerce, assistant | src/server.ts, app.ts |
apps/admin-web | React SPA (CF Pages) | merchant/admin UI: theme code editor + visual customizer, onboarding, settings | src/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.tsin-process +worker.mjs/isolate.tsisolate), 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-repo—forTenant(id).getTenant(), deny-by-default.data-provisioning— store/domain creation.control-plane-client— a generated typed SDK for the admin API (bearer auth: Clerk orrat_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 imageComposition 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.