From ad577fe7b680ad31b51b4a066dc59f08435fa1e2 Mon Sep 17 00:00:00 2001 From: kpcto Date: Thu, 27 Aug 2026 08:16:11 +0000 Subject: [PATCH] Add architecture docs --- docs/architecture.md | 95 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 docs/architecture.md diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..a9d7d79 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,95 @@ +# Architecture + +## Executive decision (§1) + +EPPP is implemented as a **TypeScript modular monolith**: Node.js 24 LTS, Fastify 5, PostgreSQL 18 (sole canonical DB), React 19 (server-rendered public components + Vite admin), and Docker Compose as the primary runtime model. + +The first visible release is intentionally small: editable site identity, editable Home page, ordered list of published posts, individual post pages, administration interface, and the **Amber** theme. + +**Central product rule:** EPPP v0.1 is small because optional features are absent, not because extensibility is absent. + +From the first release the codebase has explicit versioned boundaries for: content types, content blocks, page sections, themes, extensions, extension settings, navigation, visitor preferences, database migrations, media storage, background jobs, events, and rendering. + +EPPP is **not** a microservice system, a Next.js app, a WordPress clone, or a browser-installable arbitrary-code marketplace. + +## Architecture style (§4) + +**Modular monolith.** One application image contains the public server, admin API, rendering pipeline, extension runtime, core services, built admin assets and first-party extensions. An optional worker uses the same image with a different command (`eppp serve` / `eppp worker`). + +``` + Internet + | + Optional edge proxy (Caddy 2.11.4) + | + +---------------------------+ + | EPPP APP — Node 24 / | + | Fastify 5 | + | Public React SSR | Admin | + | React + Vite | + +------------+---------------+ + | + Application Core + Sites | Content | Revisions | Pages | Navigation | Media + Blocks | Themes | Extensions | Settings | Preferences + Auth | Jobs | Events | Rendering + | + Registry layer + ContentType | Block | Section | Theme | Extension | Job + | + PostgreSQL 18.6 +``` + +**Why not microservices:** no v1 requirement justifies distributed transactions, service discovery, brokers, multi-service deployment, or cross-service schema changes. Module boundaries allow future extraction on evidence. + +## Repository & dependency architecture (§9) + +``` +eppp/ + apps/server · apps/admin + packages/contracts · core · extension-sdk · database-postgres · rendering-react · ui-core · config · testing + extensions/core-blog · theme-amber + migrations/ · tooling/ · docs/{adr, extension-development, operations} + Dockerfile · compose.yml · compose.edge.yml · .env.example · pnpm-workspace.yaml · pnpm-lock.yaml · package.json · tsconfig.base.json +``` + +### Dependency direction (§9.1) + +Allowed: +- `apps/server -> core/application -> contracts` +- `extensions/* -> extension-sdk -> contracts` +- `database-postgres -> core ports -> Kysely/pg` + +Forbidden: +- `core -> theme-amber`, `core -> core-blog`, `core -> reading` +- `theme-amber -> database internals` +- extension A → extension B internals +- public browser code → server-only package + +CI enforces these via import/dependency architecture tests (FIT-001, FIT-010, FIT-011). + +## Core ownership (§11) + +**Core owns:** application lifecycle; site identity/configuration; authentication; content persistence primitives; revisions; page composition; navigation; extension discovery/activation/settings; theme resolution; content-type/block/section/theme registries; visitor preferences; media storage port; job registry; event bus; migration coordinator; public rendering pipeline; admin API infrastructure; health/readiness; error/observability conventions; hardened outbound fetch (v1.1, §29-A.4); page metadata rendering (v1.1, §34-A). + +**Core does not own** (extensions own these): Reading/bookmarks, bookmark seams/half-life, RSS, Amber-specific CSS, projects/running-list, comments, analytics, newsletter integrations, link-health. + +## Public rendering (§19) + +Public pages are server-rendered first. Pipeline: Fastify route → SiteResolver → VisitorPreferenceResolver → ContentService → PageComposition + BlockRegistry → ThemeResolver → React DOM server renderer → HTML. + +**Zero-JavaScript baseline (§19.1):** Home, article, navigation, metadata and footer work with JS disabled. React is a server-rendering detail; the site is not hydrated. Core public Home/article target: **0 bytes** EPPP JavaScript. + +**Client islands (§19.2):** only truly interactive features register islands (theme selector, Reading filters, graph, comments, gallery), each with an activation mode (`load`/`idle`/`visible`/`interaction`). + +## Administration (§20) + +React client app built with Vite, served by the same image. `/admin/*` → `/api/admin/v1/*`. Initial IA: Dashboard, Content→Posts, Home, Navigation, Appearance (Active theme, Theme settings), Extensions, Settings→Site. + +## v0.1 public surface (§42) + +``` +GET / · GET /posts/:slug · GET /assets/* · GET /media/* +GET /health/live · GET /health/ready · GET /admin/* +``` + +Admin API lives under `/api/admin/v1/*`. Initial Home composition: `core.site-intro` + `core.post-list`; post list defaults to published only, newest first, deterministic ordering, configurable limit.