docs(plan-9): doc-pass slice 1 — CLAUDE.md, core-shared AGENTS, architecture, plan-8 annotations
First slice of the combined Plan 8 + Plan 9 doc-update pass: - CLAUDE.md Key Conventions: append schema-in-use-case, presenter, controller unknown input, feature-scoped tRPC error mapping, public surface split (./ui) - packages/core-shared/AGENTS.md: document defineErrorMiddleware export + t re-export from trpc/init - docs/superpowers/plans/2026-05-05-plan-8-*.md and matching spec: one-line note that some controller/router patterns shifted in Plan 9; link to the Plan 9 refactor log - docs/architecture/overview.md: data-flow box now shows xProcedure + xInputSchema + xOutputSchema.parse + presenter + middleware lanes; three explanatory paragraphs added (schemas, presenter, error mapping) - docs/architecture/dependency-flow.md: app-side ./ui subpath note, allowed/disallowed examples updated for Plan 9 paths Remaining doc-pass items (root AGENTS.md, per-feature AGENTS.md ×5, core-testing AGENTS.md, adding-a-feature.md, tdd-workflow.md, testing-strategy.md, vertical-feature-spec.md) follow in subsequent commits — to be dispatched in parallel.
This commit is contained in:
@@ -29,18 +29,26 @@ packages/
|
||||
|
||||
```
|
||||
React component
|
||||
↓ useQuery(trpc.blog.articleBySlug.queryOptions(...)) ← ui/query.ts (typed tRPC client)
|
||||
↓ useQuery(trpc.blog.articleBySlug.queryOptions({slug})) ← @repo/<feature>/ui (queries)
|
||||
HTTP /api/trpc
|
||||
↓
|
||||
tRPC procedure ← integrations/api/router.ts
|
||||
tRPC procedure (xProcedure.input(xInputSchema)) ← integrations/api/router.ts
|
||||
↓ xProcedure has defineErrorMiddleware applied ← integrations/api/procedures.ts
|
||||
↓ container.get<IXController>(SYMBOL)
|
||||
Controller factory (Zod safeParse) ← interface-adapters/controllers/<verb-noun>.controller.ts
|
||||
↓ (useCase) => async (input) => result
|
||||
Use case factory ← application/use-cases/<verb-noun>.use-case.ts
|
||||
↓ (deps) => async (input) => result; deps injected by container
|
||||
Repository implementation ← infrastructure/repositories/<noun>.repository.ts
|
||||
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
|
||||
@@ -50,6 +58,27 @@ as arguments and return the callable. The container wires them via
|
||||
`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
|
||||
|
||||
Reference in New Issue
Block a user