1
Architecture
kpcto edited this page 2026-08-27 17:22:04 +00:00

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.