# Architecture Overview A vertical-feature monorepo. Business capabilities are top-level packages; non-business foundations are `core-*`. ## Package map ``` packages/ # Foundation (no business logic) core-shared/ Generic primitives — Payload field/block helpers, tRPC init/context, lib utilities core-cms/ Composition only: assembles feature CMS exports into one Payload config core-api/ Composition only: aggregates feature tRPC routers into one appRouter core-trpc/ Frontend tRPC client + per-framework providers (Next.js, TanStack) core-ui/ Design-system primitives (atoms, molecules, generic organisms, templates) # Business capabilities auth/ Users + sign-in/sign-up/sign-out + session/cookie domain blog/ Articles collection + publishing flow media/ Media upload collection (skeleton; expand with optimization, CDN, etc.) marketing-pages/ Pages collection + SiteSettings global navigation/ Header global + menu items # Tooling core-eslint/ Shared ESLint flat config + boundary rules core-typescript/ Shared tsconfig + vitest base ``` ## Data flow ``` React component ↓ useQuery(trpc.blog.articleBySlug.queryOptions(...)) ← ui/query.ts (typed tRPC client) HTTP /api/trpc ↓ tRPC procedure ← integrations/api/router.ts ↓ .input(zod).query(...) Controller (Zod safeParse) ← interface-adapters/controllers/ ↓ Use case ← application/use-cases/ ↓ container.get(SYMBOL) Repository implementation ← infrastructure/repositories/ (@injectable) ↓ getPayload({ config }) Payload Local API → Postgres ``` ## Three enforcement layers 1. **`package.json` deps** — only declare allowed deps 2. **`exports` map** — each package exposes a small public surface (`.`, `./cms`, `./api`, `./di/bind-production`) 3. **Two parallel automated checks**: - **ESLint `eslint-plugin-boundaries`** (lint-time) — enforces boundary rules at linting - **Turborepo `boundaries`** (build-graph time) — validates entire workspace dependency graph, including transitive reaches Both use the same five-tag model; see "Five tags" section below. ## Five tags The workspace is organized into five mutually exclusive tags: - **app** (4 packages): `apps/cms`, `apps/web-next`, `apps/web-tanstack`, `apps/storybook` - **core-composition** (3 packages): `packages/core-api`, `packages/core-cms`, `packages/core-trpc` - **core** (2 packages): `packages/core-shared`, `packages/core-ui` - **feature** (5 packages): `packages/auth`, `packages/blog`, `packages/media`, `packages/marketing-pages`, `packages/navigation` - **tooling** (2 packages): `packages/core-eslint`, `packages/core-typescript` **Allowed dependency directions:** | Tag | May depend on | |---|---| | app | app, core, core-composition, feature, tooling | | core-composition | core, core-composition, feature, tooling | | core | core, core-composition, tooling | | feature | core, tooling | | tooling | tooling | **Composition exceptions:** - `core-api` may import from `@repo//api` subpath exports only - `core-cms` may import from `@repo//cms` subpath exports only - `core-trpc` reaches features transitively through `core-api`'s `AppRouter` type ## Per-feature DI containers Each feature owns its own InversifyJS `Container` + symbol table. No shared symbols, no cross-feature DI coupling. Tests rebind per feature without touching others. Apps call `bindProduction*(config)` per feature at boot to swap the default mock implementations for Payload-backed ones. ## Spec reference `docs/architecture/vertical-feature-spec.md` is the canonical design.