Skip to content

Commit 09de9ce

Browse files
committed
docs(spec): FR-008 — universal React/UI hookup (per-port REST routes + Angular 18 client + API conformance)
Captures 6 sub-projects to close the universal-UI gap: - 2.1 Java Spring @RestController codegen (Spring Boot 3 / Java 21) - 2.2 Kotlin controller codegen (Spring-Kotlin first; Ktor follow-up) - 2.3 Python FastAPI APIRouter codegen - 2.4 C# RoutesGenerator refinement + Angular hookup polish (.NET 8 / C# 12) - 2.5 Angular 18 client tier — client/web/packages/angular/ runtime + server/typescript/packages/codegen-ts-angular/ codegen (universal browser client; consumes any backend via the API contract) - 2.6 API contract conformance corpus (fixtures/api-contract-conformance/) + per-port HTTP runners that verify routes match the URL grammar + wire format identically Tier 1 invariants: URL grammar, filter operator vocabulary, HTTP status codes, JSON wire format (currency minor-units, ISO dates, withCount envelope). Tier 2: each port's idiomatic controller/DTO/component style. Angular 18 lives in TS workspace (Angular IS TypeScript); C# .NET 8 backends consume it via the universal contract + a docs recipe for CORS / base-URL wiring. NgModules-free (standalone components only); signals-based reactivity where idiomatic. Each sub-project gets its own brainstorm → spec → plan → impl cycle. This FR is the umbrella that locks the cross-port contract. Out of scope: GraphQL, WebSockets, auth/authz middleware, OpenAPI Tier 1 contract (separate follow-up), Svelte/React Native/Vue clients.
1 parent dcc1b22 commit 09de9ce

1 file changed

Lines changed: 224 additions & 0 deletions

File tree

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# FR-008 — Universal React/UI hookup: per-port REST route codegen + Angular 18 client + API contract conformance
2+
3+
- **Date:** 2026-05-26
4+
- **Status:** Design — plan-of-record. Captures the cross-port work needed so any backend (TS / Java / Kotlin / C# / Python) can serve a universal browser-side UI client (React/TanStack today, Angular 18 added by this FR).
5+
- **Target version:** 7.0.0 + follow-ups
6+
- **Scope:** Five workstreams that together close the React/UI hookup gap across all language ports:
7+
1. **Java route codegen** — Spring `@RestController` per entity matching the cross-port URL grammar
8+
2. **Kotlin route codegen** — Spring-Kotlin or Ktor controller per entity (substrate decision needed)
9+
3. **Python route codegen** — FastAPI `APIRouter` per entity
10+
4. **C# route codegen refinement** — gap audit + Angular 18 client integration for ASP.NET Minimal API backends (already shipped)
11+
5. **Angular 18 client tier**`client/web/packages/angular/` runtime + `server/typescript/packages/codegen-ts-angular/` codegen (universal browser client; consumes any backend via the API contract)
12+
6. **API contract conformance corpus** — cross-port runner that verifies every backend's emitted routes conform to the URL grammar + wire format
13+
14+
## 1. Background
15+
16+
The TS browser client (`client/web/packages/{runtime-web,react,tanstack}`) is **architecturally universal** — any backend implementing the URL grammar + wire format from [`docs/features/api-contract.md`](../../docs/features/api-contract.md) can serve it. The contract:
17+
18+
- `GET /api/<entity>?filter[field][op]=value&sort=field:asc&limit=N&offset=N`
19+
- `GET /api/<entity>/:id`
20+
- `POST /api/<entity>` / `PATCH /api/<entity>/:id` / `PUT /api/<entity>/:id` / `DELETE /api/<entity>/:id`
21+
- 8 filter operators (eq / ne / gt / gte / lt / lte / in / like / isNull) gated by field subtype
22+
- JSON wire format, currency as integer minor units, ISO 8601 dates, `?withCount=1` pagination envelope
23+
24+
**Today's state:**
25+
26+
| Port | Route codegen | Status |
27+
|---|---|---|
28+
| TypeScript | `routesFile()` → Fastify via `runtime-ts/drizzle-fastify` | ✓ shipped |
29+
| C# | `RoutesGenerator.cs` → ASP.NET Minimal API | ✓ shipped (needs Angular-side polish) |
30+
| Java || ✗ consumers hand-write Spring controllers |
31+
| Kotlin || ✗ consumers hand-write Spring-Kotlin or Ktor handlers |
32+
| Python || ✗ consumers hand-write FastAPI routers |
33+
34+
For the React client, the TS + C# backends light up end-to-end; the other three require consumers to hand-write controllers that conform.
35+
36+
For the Angular client, **none** of the backends ship a codegen story today — adding it is part of this FR. The Angular runtime + codegen package live under TypeScript (`client/web/packages/angular/` + `server/typescript/packages/codegen-ts-angular/`) because Angular IS TypeScript; the codegen output is consumed by any backend the same way as React.
37+
38+
## 2. Decomposition into 5 sub-projects
39+
40+
Each sub-project gets its own implementation plan; this FR is the umbrella that locks the cross-port contract + tracks the matrix.
41+
42+
### 2.1 Java route codegen (Spring Boot 3 / Java 21)
43+
44+
New module: `server/java/codegen-spring/` (mirrors `codegen-kotlin/`).
45+
46+
- Extends `codegen-base`'s `MultiFileDirectGeneratorBase<MetaObject>`
47+
- One generator per concern (mirrors TS pattern):
48+
- `SpringControllerGenerator` — emits `<Entity>Controller.java` with `@RestController` + 5 CRUD endpoints
49+
- `SpringDtoGenerator` — emits `<Entity>Dto.java` records (Java 21 records) for request/response — separates wire from persistence entity
50+
- `SpringFilterAllowlistGenerator` — emits server-side filter allowlist per entity (mirrors TS Project D)
51+
- Filter operators wired via Spring Data `Specification` OR raw Criteria API
52+
- Pagination + sort via `Pageable` from Spring Data; output normalized to the wire `{ rows, total }` envelope
53+
- Currency: minor-units `long` throughout; no conversion at the controller layer
54+
- Targets: Spring Boot 3.x (Spring Web MVC; not Spring WebFlux — consumers can wire WebFlux separately if needed)
55+
- New deps to scope: `metaobjects-codegen-base` + `metaobjects-omdb` (or `metaobjects-metadata` only, and route codegen is substrate-agnostic; consumer wires their own persistence — TBD design choice).
56+
57+
### 2.2 Kotlin route codegen — substrate decision
58+
59+
**Option A: Spring-Kotlin controllers.** New generator in `codegen-kotlin/` (already a Kotlin module). Emits `<Entity>Controller.kt` with `@RestController` + Kotlin extension functions. Plays nicely with existing `KotlinSpringConfigGenerator`.
60+
61+
**Option B: Ktor route handlers.** New generator emitting `<Entity>Routes.kt` with `Route.<Entity>Routes()` extension installing the 5 CRUD endpoints on a Ktor `Application`. More Kotlin-native; not Spring-coupled.
62+
63+
**Recommendation:** ship Spring-Kotlin first (party-lore-class consumers are Spring-based). Add Ktor as a follow-up substrate once a Ktor-stack consumer surfaces.
64+
65+
### 2.3 Python route codegen (FastAPI)
66+
67+
Extends Python's codegen module (lives at `server/python/src/metaobjects/codegen/` or similar — verify existing layout when implementation begins).
68+
69+
- Emits `<entity>_router.py` with FastAPI `APIRouter` + 5 CRUD endpoints
70+
- Pydantic v2 request/response models (consumers already use Pydantic per the existing `field_types` codegen output)
71+
- Pagination + sort via Query params; filter via the same URL grammar
72+
- Currency: minor-units `int`; no float conversion
73+
- Targets: FastAPI 0.110+; consumer wires `APIRouter` instances into their FastAPI `app`
74+
75+
### 2.4 C# route refinement + Angular 18 hookup
76+
77+
`MetaObjects.Codegen/Generators/RoutesGenerator.cs` already ships ASP.NET Minimal API routes for .NET 8 / C# 12. The refinement work:
78+
79+
- **Gap audit** vs the cross-port URL grammar — verify filter operators, pagination envelope, sort syntax all conform
80+
- **withCount=1 envelope** support — ensure the routes return `{ rows, total }` when requested
81+
- **PATCH vs PUT** — match the TS contract (TS supports both verbs for updates; C# should too)
82+
- **CORS preflight** helpers in the consumer setup recipe (so an Angular dev-server on port 4200 can call an ASP.NET backend on port 5000)
83+
- **Angular-side integration** — generated Angular services target ASP.NET Minimal API URL conventions cleanly (no special-casing needed; this is the validation that the cross-port grammar holds)
84+
85+
### 2.5 Angular 18 client tier (NEW universal client)
86+
87+
**Two new packages on the TypeScript side:**
88+
89+
#### `client/web/packages/angular/` — Angular 18 runtime
90+
91+
Mirrors `client/web/packages/react/` shape but for Angular. Universal — works with any backend that conforms to the API contract.
92+
93+
- **`EntityFetcherToken`** — Angular `InjectionToken` for the `EntityFetcher` function (consumer provides via `provideEntityFetcher(fetcher)`)
94+
- **`CurrencyInputComponent`** (`<mo-currency-input>`) — standalone Angular component mirroring React's `<CurrencyInput>` for minor-units bidirectional binding
95+
- **`EntityGridComponent`** (`<mo-entity-grid>`) — standalone component over TanStack Table Angular adapter (`@tanstack/angular-table` 8.x)
96+
- **`CellRendererRegistry`** — Angular DI-friendly registry for cell renderer overrides per view subtype
97+
- **`buildFilterQs`** — re-export from `runtime-web` (framework-agnostic; no Angular-specific re-implementation needed)
98+
- **`formatCurrency` / `parseCurrency`** — re-export from `runtime-web`
99+
- Angular 18 specifically: standalone components only (no NgModules), signals-based reactivity where idiomatic
100+
101+
#### `server/typescript/packages/codegen-ts-angular/` — Angular codegen
102+
103+
Mirrors `codegen-ts-react/` + `codegen-ts-tanstack/` pair. One package; emits both forms + service code.
104+
105+
- **`angularServiceFile()`** — emits `<Entity>.service.ts` per entity: `@Injectable({ providedIn: 'root' })` class wrapping the EntityFetcher with typed methods (`list`, `get(id)`, `create(dto)`, `update(id, patch)`, `delete(id)`)
106+
- **`angularFormFile()`** — emits `<Entity>.form.component.ts` per entity: standalone form component using Angular reactive forms + the metadata-driven validators (mirrors React's `useEntityForm`)
107+
- **`angularGridFile()`** — emits `<Entity>.grid.component.ts` per entity with `layout.dataGrid` metadata: standalone grid component pre-wired with column defs derived from metadata + TanStack Angular Table integration
108+
- **Per-entity opt-out**: `@emitAngular: false` on an entity skips all three Angular outputs
109+
110+
#### Why this lives in the TS workspace, not C#
111+
112+
Angular IS TypeScript — the generated output is `.ts` files consumed by an Angular CLI project. The codegen package is TS that emits TS. The natural home is `server/typescript/packages/codegen-ts-angular/` (parallel to `codegen-ts-react` + `codegen-ts-tanstack`), and the runtime package is `client/web/packages/angular/` (parallel to `client/web/packages/react`).
113+
114+
**C# .NET 8 / C# 12 backends consume the Angular client the same way they'd consume the React client** — via the API contract. The "C# + Angular 18" combination becomes a documented recipe in `docs/recipes/csharp-angular18.md` (new) explaining the CORS + base-URL wiring, not a new codegen module under `server/csharp/`.
115+
116+
### 2.6 API contract conformance corpus
117+
118+
New shared corpus: `fixtures/api-contract-conformance/` (NEW). Goal: any backend's emitted routes should pass this corpus' assertions identically.
119+
120+
Fixtures shape:
121+
122+
```
123+
fixtures/api-contract-conformance/<scenario-name>/
124+
├── meta.json # canonical metadata for the entity under test
125+
├── seed.json # seed rows
126+
├── requests.yaml # ordered HTTP requests (method + path + body)
127+
└── expected.json # per-request expected status + body shape
128+
```
129+
130+
Per-port runner (one per shipped backend codegen, including TS + C# already-shipped + Java/Kotlin/Python/C#-Angular-route after FR-008.x lands):
131+
132+
1. Generate routes for the fixture's metadata
133+
2. Run the generated backend (TS: Fastify; Java: Spring Boot test slice; Kotlin: same; C#: WebApplicationFactory; Python: FastAPI TestClient)
134+
3. Execute `requests.yaml` against the running backend via HTTP
135+
4. Assert each response matches `expected.json` (status + body, normalized for non-deterministic fields like timestamps)
136+
137+
Scenarios to ship Day 1 (12+):
138+
139+
- `list-empty` — GET /api/Author → `[]`
140+
- `list-with-pagination` — GET /api/Author?limit=10&offset=20 → page slice
141+
- `list-with-withcount` — GET /api/Author?limit=10&withCount=1 → `{ rows, total }`
142+
- `filter-eq` / `filter-ne` / `filter-gt` / `filter-like` / `filter-in` / `filter-isnull` — one per operator
143+
- `sort-asc` / `sort-desc`
144+
- `get-by-id` / `get-by-id-not-found`
145+
- `create-201` / `create-400-validation-error`
146+
- `update-patch` / `update-put`
147+
- `delete-204` / `delete-not-found`
148+
149+
This is the analog of `persistence-conformance/` for the API tier. Reuses the same canonical entity (`Author` / `acme::blog`) so the metadata stays shared.
150+
151+
## 3. Cross-port classification per `cross-language-porting`
152+
153+
### Tier 1 — invariant (must match cross-port)
154+
155+
- URL grammar (paths + verbs + filter syntax + sort syntax + pagination params)
156+
- Wire format (JSON, currency as integer minor-units, ISO 8601 dates, withCount envelope shape)
157+
- HTTP status codes (200 / 201 / 204 / 400 / 404)
158+
- Filter operator vocabulary (eq / ne / gt / gte / lt / lte / in / like / isNull) + subtype gating
159+
160+
### Tier 2 — idiomatic per port
161+
162+
- Spring `@RestController` annotations vs ASP.NET `MapGet` vs Fastify `app.get` vs FastAPI `@router.get` vs Ktor `route { get { } }`
163+
- Java `Pageable` vs FastAPI `Query` params vs Spring `@RequestParam`
164+
- DTO/record shapes (Java records, Kotlin data classes, C# records, Pydantic models, Zod schemas)
165+
- Error response idiom (Spring `@ControllerAdvice` vs FastAPI exception handlers vs ASP.NET ProblemDetails)
166+
- Angular vs React component API (Angular standalone components + DI; React hooks + context)
167+
168+
### Tier 3 — internal / free
169+
170+
- Test runner choice per port
171+
- Dev-server port conventions
172+
173+
## 4. Driving consumers
174+
175+
- **Party-lore-class** (Spring Boot + Kotlin + Exposed + Postgres): wants 2.2 (Kotlin Spring controllers)
176+
- **C# .NET 8 + Angular 18 adopter** (new addition this FR): wants 2.4 (C# refinement) + 2.5 (Angular client) + a docs recipe
177+
- **Future TS-only React adopter**: covered today, gets 2.5 Angular as an alternate client option
178+
- **Future Python/FastAPI adopter**: wants 2.3
179+
- **Future Java/Spring adopter**: wants 2.1
180+
181+
## 5. Out of scope
182+
183+
- **GraphQL** route codegen — REST only Day 1; GraphQL can be a separate FR
184+
- **WebSocket / SSE** route codegen — REST only Day 1
185+
- **Authentication / authorization** middleware — out of scope; consumer wires Spring Security / `[Authorize]` / FastAPI dependencies / Ktor auth around the generated routes
186+
- **OpenAPI schema generation** — useful but separate; Spring + ASP.NET + FastAPI already emit OpenAPI natively from the generated controllers. Codifying a cross-port OpenAPI Tier 1 contract is a follow-up FR.
187+
- **Svelte / React Native / Vue clients** — defer; the Angular addition validates that the universal client pattern scales beyond React/TanStack.
188+
189+
## 6. Ordering + dependencies
190+
191+
Each sub-project (2.1–2.6) is independently implementable. Recommended sequence based on likely consumer demand:
192+
193+
1. **2.5 Angular client tier** first — unblocks the C#-Angular adopter immediately (C# routes already ship). Validates the universal-client architecture.
194+
2. **2.4 C# refinement** — small audit; lands in parallel with 2.5.
195+
3. **2.2 Kotlin Spring controllers** — party-lore-class consumer.
196+
4. **2.6 API contract conformance corpus** — once 2+ ports have route codegen, the corpus has something to verify cross-port.
197+
5. **2.1 Java Spring controllers** — when a Spring/Java consumer surfaces.
198+
6. **2.3 Python FastAPI routes** — when a FastAPI consumer surfaces.
199+
200+
Each gets its own brainstorm → spec → plan → impl cycle. This FR is the umbrella spec; individual sub-project specs live at `docs/superpowers/specs/2026-XX-XX-fr-008-<sub-project>-design.md`.
201+
202+
## 7. Risks
203+
204+
1. **Angular 18 standalone-components-only is a recent default** — older Angular tutorials reference NgModules. Mitigation: codegen output uses standalone components throughout; recipe documents this explicitly.
205+
2. **CORS + dev-server proxying** is consumer-specific — Angular dev-server on port 4200, ASP.NET on port 5000, Spring on 8080. Mitigation: per-recipe documentation; not a codegen concern.
206+
3. **TanStack Angular Table 8.x** maturity — newer than React equivalent. Mitigation: snapshot test the Angular grid output; if TanStack Angular Table doesn't fit, fall back to AG Grid or a hand-rolled table — decision deferred to 2.5 implementation time.
207+
4. **OpenAPI Tier 1 invariant** absence may let backends drift on response shapes — mitigation is the API contract conformance corpus (2.6).
208+
5. **Spring controller codegen** (2.1) must not collide with consumers' existing manually-written controllers — mitigation: `@generated` headers + overwrite policy (same as Java codegen-mustache today).
209+
210+
## 8. Versioning + compatibility
211+
212+
- Target: `7.0.0+` for each sub-project as it lands
213+
- The API contract (URL grammar + wire format) is **frozen Tier 1** — any future change requires bumping the major version and updating every port's runner in lockstep
214+
- Generator names are Tier 1 invariants per sub-project's spec
215+
216+
## 9. Cross-references
217+
218+
- [`docs/features/api-contract.md`](../../docs/features/api-contract.md) — the cross-port REST contract this FR builds against
219+
- [`docs/ports/typescript-client.md`](../../docs/ports/typescript-client.md) — the React/TanStack client; Angular adds a peer
220+
- TS reference: `server/typescript/packages/codegen-ts/src/templates/routes-file.ts` + `runtime-ts/drizzle-fastify`
221+
- C# reference: `server/csharp/MetaObjects.Codegen/Generators/RoutesGenerator.cs`
222+
- Kotlin codegen pattern: [`docs/superpowers/specs/2026-05-25-codegen-kotlin-design.md`](2026-05-25-codegen-kotlin-design.md)
223+
- Java codegen pattern: `server/java/codegen-base/` + `server/java/codegen-mustache/`
224+
- Universal client architecture: CLAUDE.md "Framework integration: separate codegen and runtime packages"

0 commit comments

Comments
 (0)