Files
agentic-dev/docs/architecture/overview.md
Danijel Martinek 7c915cb447 docs(architecture): surface core-audit + DPA across architecture docs
Touches the deeper architecture surfaces the Phase 6 sweep skipped:

- overview.md: split must-have (core-shared, core-cms, core-api) from
  optional (core-trpc, core-ui, core-realtime, core-events, core-audit);
  add core-audit to the Five tags optional list
- dependency-flow.md: extend the bindAll diagram with resolveAudit;
  add auditLog row to the BindContext table; rename the
  TRACER/LOGGER/METRICS heading to include AUDIT (ADR-018); note the
  R52-style boundary rule for @repo/core-audit (consume via protocol)
- vertical-feature-spec.md: target-state section now states 3 must-have
  + 5 optional cores; tag matrix includes the optional cores; bind-
  production signature destructure includes auditLog
- di-explainer.html: §08 instrumentation gains an IAuditLog block + the
  Wiring path tree shows resolveAudit + auditLog in ctx
- testing-strategy.md: RecordingAuditLog reference + reset() guidance
2026-05-11 17:06:58 +02:00

135 lines
7.2 KiB
Markdown

# Architecture Overview
A vertical-feature monorepo. Business capabilities are top-level packages; non-business foundations are `core-*`.
## Package map
```
packages/
# Must-have foundation (no business logic)
core-shared/ Generic primitives — Payload field/block helpers, tRPC init/context,
instrumentation interfaces, audit protocol + truncate-ip + AuditEntry shape,
BindContext + protocol types for optional cross-cutting cores
core-cms/ Composition only: assembles feature CMS exports into one Payload config
core-api/ Composition only: aggregates feature tRPC routers into one appRouter
# Optional cross-cutting cores (scaffold on demand via `pnpm turbo gen core-package <name>`)
core-trpc/ Frontend tRPC client + per-framework providers (Next.js, TanStack)
core-ui/ Design-system primitives (atoms, molecules, generic organisms, templates)
core-realtime/ Socket.IO server + broadcaster + handler registry (ADR-016)
core-events/ In-memory + Payload-backed event bus + job queue (ADR-015)
core-audit/ DPA-compliant audit logging — sinks, hook factories, eraseSubject (ADR-018)
# 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
```
See `docs/architecture/template-tiers.md` for the must-have/optional split and the scaffold commands.
## Data flow
```
React component
↓ useQuery(trpc.blog.articleBySlug.queryOptions({slug})) ← @repo/<feature>/ui (queries)
HTTP /api/trpc
tRPC procedure (xProcedure.input(xInputSchema)) ← integrations/api/router.ts
↓ xProcedure has defineErrorMiddleware applied ← integrations/api/procedures.ts
↓ container.get<IXController>(SYMBOL)
Controller factory (xInputSchema.safeParse) ← interface-adapters/controllers/<verb-noun>.controller.ts
↓ (useCase) => async (input: unknown) => Promise<view>
Use case factory ← application/use-cases/<verb-noun>.use-case.ts
↓ (deps) => async (input: XInput) => XOutput
↓ ends with xOutputSchema.parse(result)
Repository implementation ← infrastructure/repositories/<noun>.repository.ts
↓ getPayload({ config })
Payload Local API → Postgres
↓ on throw:
domain error → defineErrorMiddleware → TRPCError(code, cause)
↓ on success:
controller's `function presenter(value: XOutput)` shapes the view
tRPC response
```
Use cases and controllers are **factory functions** — they take their dependencies
as arguments and return the callable. The container wires them via
`.toDynamicValue((ctx) => factoryFn(ctx.container.get(...)))`. Each exports
`export type I*UseCase = ReturnType<typeof xUseCase>` (and the analogous
`I*Controller`) so consumers can depend on the type without importing the impl.
Controllers are **one per use case** — no multi-method controller files.
**Schemas live in the use-case file** (Plan 9): every use case exports
`xInputSchema` (a `z.ZodObject` with `.strict()`; `z.object({}).strict()` for
void inputs) and, for non-void use cases, `xOutputSchema`. Controllers and tRPC
procedures import the schema — never redefine it. The use case body validates
its output via `xOutputSchema.parse(...)` before returning, so a misbehaving
repository fails loudly at the layer that owns the contract.
**Controllers reshape via a co-located presenter** (Plan 9): every non-void
controller defines a top-level `function presenter(value: XOutput)` and returns
`Promise<ReturnType<typeof presenter>>`. Identity is fine — `return value;`
but the function form is always present so adding a transform later is a
one-line edit. Void controllers (e.g. `signOutController`,
`deleteMediaController`) return `Promise<void>` and skip the presenter.
**Domain errors map to `TRPCError` per feature** (Plan 9): each feature owns
`integrations/api/procedures.ts` exporting `xProcedure = t.procedure.use(
defineErrorMiddleware([[Ctor, "TRPC_CODE"], ...]))`. Routers use `xProcedure`
instead of bare `publicProcedure`. `core-shared` provides the
`defineErrorMiddleware` factory but never enumerates a feature's errors —
each feature passes its own constructors in.
## 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** (2 must-have): `packages/core-api`, `packages/core-cms`. Plus `packages/core-trpc` when scaffolded via `pnpm turbo gen core-package trpc` (optional).
- **core** (1 must-have): `packages/core-shared`. Plus `packages/core-ui`, `packages/core-realtime`, `packages/core-events`, `packages/core-audit` when scaffolded via `pnpm turbo gen core-package <name>` (optional).
- **feature** (5 packages): `packages/auth`, `packages/blog`, `packages/media`, `packages/marketing-pages`, `packages/navigation`
- **tooling** (2 packages): `packages/core-eslint`, `packages/core-typescript`
See `docs/architecture/template-tiers.md` for the must-have/optional split and the scaffold commands.
**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/<feature>/api` subpath exports only
- `core-cms` may import from `@repo/<feature>/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.