Skip to content

Commit 66ba667

Browse files
dmealingclaude
andcommitted
docs: fix launch-blocking doc inaccuracies (quickstarts + underclaim sweep)
Pre-launch review found the newcomer quickstarts broken as written and ~8 docs under-claiming shipped features (Java/Kotlin/Python route+filter+payload-VO+output-parser codegen, Python ObjectManager, C# NuGet publication) while adjacent files said "shipped". Code was verified solid; only the docs were wrong. Newcomer-critical (re-ran every corrected snippet for real): - typescript.md: fixed the flagship "Use" example (real flat path/authorRoutes/Fastify-plugin shape) + documented the working `meta migrate --from-db ... --apply` init path (baseline silently no-ops for a new project) + db.ts wiring + fastify/ESM/tsconfig notes. - python.md: fixed the loader-API "Use" example (real imports + root.children()/get_meta_attr), the broken `python -m` gen/verify commands (→ metaobjects console-script), the providers example, @DataClass→BaseModel, pydantic/fastapi + 3.11+ notes, ObjectManager "roadmap"→shipped. - server/python/README.md (PyPI page): removed the false `migrate`-module + SQLAlchemy claims. Underclaim/self-contradiction sweep (verified each flip against source): - README matrix, api-contract.md (self-contradicted 4×), csharp.md (NuGet 0.19.3), java.md, entities.md, templates-and-payloads.md, migrations-and-drift.md, typescript-client.md, llms-full.txt, docs/README.md — now accurately reflect the shipped state. Refs the pre-launch review (.superpowers/sdd/launch-review-*.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XeGSV3StPCcJGZNNJ4ZfAb
1 parent 40d1867 commit 66ba667

13 files changed

Lines changed: 338 additions & 149 deletions

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ first-week wedge plan — and `meta init` picks up from there.
8888
|---|---|---|---|
8989
| TypeScript | Published to npm at `0.19.3` (the `@metaobjectsdev/*` packages) | [`docs/ports/typescript.md`](docs/ports/typescript.md) | [`server/typescript/`](server/typescript/) · [`client/web/`](client/web/) |
9090
| Java | Loader + OMDB + render + Maven plugin all shipped; full conformance green | [`docs/ports/java.md`](docs/ports/java.md) | [`server/java/`](server/java/) |
91-
| Kotlin | Codegen tier on top of Java — 7 generators (entity, Exposed table, relations, payload, validator, Spring config, storedProc); 12 / 12 persistence-conformance | [`docs/ports/kotlin.md`](docs/ports/kotlin.md) | [`server/java/codegen-kotlin/`](server/java/codegen-kotlin/) · [`server/java/metadata-ktx/`](server/java/metadata-ktx/) |
91+
| Kotlin | Codegen tier on top of Java — 9 generators (entity, Exposed table, relations, payload, output-parser, validator, Spring config, storedProc, Spring controller); 12 / 12 persistence-conformance | [`docs/ports/kotlin.md`](docs/ports/kotlin.md) | [`server/java/codegen-kotlin/`](server/java/codegen-kotlin/) · [`server/java/metadata-ktx/`](server/java/metadata-ktx/) |
9292
| C# | Loader + conformance + EF Core codegen + render engine + `dotnet meta` CLI all shipped | [`docs/ports/csharp.md`](docs/ports/csharp.md) | [`server/csharp/`](server/csharp/) |
9393
| Python | Loader + conformance + render + entity-model codegen + ObjectManager runtime shipped; schema migrations are TS-owned (ADR-0015) | [`docs/ports/python.md`](docs/ports/python.md) | [`server/python/`](server/python/) |
9494

@@ -103,26 +103,27 @@ first-week wedge plan — and `meta init` picks up from there.
103103
| `field.currency` / `field.enum` | Yes | Yes | Yes | Yes | Yes |
104104
| `field.object` + `@storage=flattened` | Yes | Yes | Yes (per-sub-field columns) | Yes (EF Core `OwnsOne`) | Loader yes; codegen partial |
105105
| Templates + render (FR-004) | Yes | Yes | Yes (wraps Java) | Yes | Yes |
106-
| Payload-VO codegen | Yes (via projection) | – (consumers use `Map`) | Yes (`@Serializable`) | Yes | – (consumers use `dict`) |
106+
| Payload-VO codegen | Yes (via projection) | Yes (`SpringPayloadGenerator`) | Yes (`@Serializable`) | Yes | Yes (`payload_vo_generator`) |
107107
| Migration emission | `meta migrate` (Postgres / SQLite / D1) | Via TS toolchain (`@metaobjectsdev/cli migrate`) | Via TS toolchain (`@metaobjectsdev/cli migrate`) | Via TS toolchain (ADR-0015) | Via TS toolchain (ADR-0015) |
108108
| DB-drift verify | `meta verify --db` | Template-drift: `Renderer.verify`; schema-drift is TS-owned (ADR-0015) | Template-drift: `Renderer.verify`; startup: `MetadataStartupValidator` | `dotnet meta verify` (codegen-drift) | Schema-drift is TS-owned (ADR-0015) |
109109
| Template-drift verify | Yes | Yes (`Renderer.verify`) | Yes (via Java) | Yes (`meta verify`) | Yes (`metaobjects.render.verify`) |
110110
| YAML authoring (sigil-free → JSON) | Yes | Yes | Yes (via Java) | Yes | Yes |
111111
| Runtime metadata (ObjectManager-style) | Yes (`runtime-ts`) | Yes (OMDB) | Yes (via Java OMDB + Exposed) | Roadmap | Yes (ObjectManager) |
112-
| React / UI client (browser) | Yes (`@metaobjectsdev/react` + `@metaobjectsdev/tanstack`; codegen + runtime) | Consumes TS client via REST | Consumes TS client via REST | Consumes TS client via REST | Consumes TS client via REST |
113-
| Cross-port REST routes for the client | Generated (`routesFile()` → Fastify) | Hand-write Spring controller per contract | Hand-write Spring-Kotlin / Ktor per contract | Generated (`RoutesGenerator` → ASP.NET Minimal API) | Hand-write FastAPI router per contract |
112+
| React / Angular UI client (browser) | Yes — React (`@metaobjectsdev/react` + `@metaobjectsdev/tanstack`) and Angular 18 (`@metaobjectsdev/angular`, published at `0.6.0`); both codegen + runtime | Consumes TS client via REST | Consumes TS client via REST | Consumes TS client via REST | Consumes TS client via REST |
113+
| Cross-port REST routes for the client | Generated (`routesFile()` → Fastify) | Generated (`SpringControllerGenerator`Spring `@RestController`, incl. filter/sort) | Generated (`KotlinSpringControllerGenerator` → Spring `@RestController`, incl. filter/sort) | Generated (`RoutesGenerator` → ASP.NET Minimal API) | Generated (`router_generator`FastAPI `APIRouter`, incl. filter/sort) |
114114

115115
A "Yes" means the feature is covered by the shared conformance corpora at
116116
[`fixtures/`](fixtures/) for that port, or by a port-local test of equivalent
117117
scope. A "partial" means the loader recognizes the metamodel feature but the
118118
codegen / runtime tier doesn't fully exercise it yet.
119119

120-
The React / UI client is TypeScript-only by construction (the browser is
121-
TS-native) but is **universal** — see
120+
The React and Angular UI clients are TypeScript-only by construction (the
121+
browser is TS-native) but are **universal** — see
122122
[`docs/features/api-contract.md`](docs/features/api-contract.md) for the
123123
URL grammar + wire format the client speaks, and
124124
[`docs/ports/typescript-client.md`](docs/ports/typescript-client.md) for
125-
the consumer-side wiring.
125+
the consumer-side wiring (React + TanStack, and the
126+
[Angular 18 tier](docs/ports/typescript-client.md#angular-18)).
126127

127128
## Four pillars
128129

@@ -178,7 +179,7 @@ metaobjects/
178179
│ └── python/ # Python port
179180
180181
└── client/
181-
└── web/ # universal browser packages (React, TanStack, framework-agnostic)
182+
└── web/ # universal browser packages (React, TanStack, Angular, framework-agnostic)
182183
```
183184

184185
## Getting started

docs/README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,40 @@ copy-paste cross-port without context-switching.
1616
docs/
1717
├── README.md # this file
1818
├── CONFORMANCE.md # inverse index: fixture → feature doc + per-port pass status
19-
├── features/ # one file per metamodel feature
19+
├── features/ # one file per metamodel feature — see `ls docs/features/`
20+
│ # for the definitive, always-current list; as of this
21+
│ # writing:
2022
│ ├── entities.md
2123
│ ├── source-kinds.md
2224
│ ├── relationships.md
2325
│ ├── field-types.md
2426
│ ├── templates-and-payloads.md
2527
│ ├── loaders.md
2628
│ ├── migrations-and-drift.md
29+
│ ├── migrations/ # per-version migration guides (e.g. 0.x → 1.0)
2730
│ ├── api-contract.md # cross-port REST contract (the universal browser client speaks it)
2831
│ ├── abstracts-and-inheritance.md # abstract: true + extends: — author-side shape reuse
2932
│ ├── extending-with-providers.md # custom metamodel subtypes/attrs via MetaDataTypeProvider
30-
│ └── yaml-authoring.md
33+
│ ├── yaml-authoring.md
34+
│ ├── cli.md # CLI command matrix across all five ports
35+
│ ├── codegen-concepts.md # declarative template-codegen scopes (perEntity/perPackage/perModel)
36+
│ ├── codegen-data-shapes.md # the neutral template data dict shared cross-port
37+
│ ├── downstream-metadata-decisions.md # guidance for adopters extending the metamodel
38+
│ ├── generated-mutations.md # generated POST/PATCH mutation surface
39+
│ ├── image-upload.md # view.image form control (TS-web)
40+
│ └── own-your-codegen.md # scaffold-and-own generator ownership (ADR-0034)
3141
└── ports/ # one file per language/framework port
3242
├── typescript.md
33-
├── typescript-client.md # browser-side TS (React, TanStack, codegen pairs)
43+
├── typescript-client.md # browser-side TS (React, TanStack, Angular, codegen pairs)
3444
├── java.md
3545
├── kotlin.md
3646
├── csharp.md
3747
└── python.md
3848
```
3949

50+
If this list and `ls docs/features/` ever disagree, trust the directory listing —
51+
this tree is documentation, not the source of truth.
52+
4053
## When to read which
4154

4255
| You want to … | Read |
@@ -48,7 +61,7 @@ docs/
4861
| Wire prompt construction (FR-004) | [`features/templates-and-payloads.md`](features/templates-and-payloads.md) |
4962
| Share a metadata shape across multiple instances (abstracts, `extends:`) | [`features/abstracts-and-inheritance.md`](features/abstracts-and-inheritance.md) |
5063
| Add a custom metamodel subtype or attribute to a downstream project | [`features/extending-with-providers.md`](features/extending-with-providers.md) + [`recipes/extending-metaobjects-with-providers.md`](recipes/extending-metaobjects-with-providers.md) |
51-
| Wire the universal browser client (React + TanStack) to any backend | [`ports/typescript-client.md`](ports/typescript-client.md) + [`features/api-contract.md`](features/api-contract.md) |
64+
| Wire the universal browser client (React + TanStack, or Angular 18) to any backend | [`ports/typescript-client.md`](ports/typescript-client.md) (see [§Angular 18](ports/typescript-client.md#angular-18) for the Angular tier) + [`features/api-contract.md`](features/api-contract.md) |
5265
| Know which feature is supported in which port today | the capability matrix in the root [`README.md`](../README.md) or the per-port "Capability snapshot" table |
5366
| See which conformance fixtures gate each feature (and which port passes which) | [`CONFORMANCE.md`](CONFORMANCE.md) |
5467
| Read the canonical spec (target-agnostic) | [`../spec/`](../spec/) |

docs/features/api-contract.md

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ Throughout the doc the worked example is an `Author` entity in the
1919

2020
The contract has two halves: the **URL grammar** (paths + query-string
2121
shape) and the **wire format** (JSON request / response bodies). Both
22-
halves are language-agnostic and stable across the four shipped
23-
language ports. Routes can be **generated** (where the port ships a
24-
route generator) or **hand-written** (where it doesn't) — the wire
25-
behavior is identical either way.
22+
halves are language-agnostic and stable across all five shipped
23+
language ports. Every port ships a generated route implementation today
24+
(see "Per-port route codegen status" below) — hand-writing a controller
25+
is an option for custom needs, not the default path.
2626

2727
## The `EntityFetcher` contract
2828

@@ -72,7 +72,7 @@ helper (so `Author` → `authors`). Generated TS hooks read `$path` from
7272
the entity-constants file, so the client and the server agree on the
7373
path segment without hand-coordination.
7474

75-
### Filter operators (8)
75+
### Filter operators (9)
7676

7777
Filters use a **bracketed qs** shape: `filter[<field>][<op>]=<value>`.
7878
A bare value (`filter[<field>]=<value>`) is sugar for `eq`. Multiple
@@ -90,12 +90,12 @@ filters AND together.
9090
The operator set is gated by field subtype in the generated
9191
`<Entity>FilterAllowlist`. A request with an operator that the field
9292
subtype doesn't support → HTTP 400. The operator list is a Tier 1
93-
cross-port invariant — every port's parser must implement these eight
94-
and only these eight.
93+
cross-port invariant — every port's parser must implement these nine
94+
and only these nine.
9595

9696
### TS-only filter extensions (not part of the cross-port contract)
9797

98-
The TypeScript runtime parser ships five filter behaviors beyond the eight
98+
The TypeScript runtime parser ships five filter behaviors beyond the nine
9999
operators. They are **NOT part of the cross-port REST contract** — the other
100100
ports (Java, Kotlin, Python, C#) do not implement them, and a relying adopter
101101
must not assume them on a non-TS backend. They are deliberately deferred until
@@ -191,17 +191,20 @@ retryable / log-only.
191191
|---|---|---|
192192
| TypeScript | shipped — `@metaobjectsdev/codegen-ts` `routesFile()` → Fastify (`@metaobjectsdev/runtime-ts/drizzle-fastify`) AND `routesFileHono()` → Hono (`@metaobjectsdev/runtime-ts/hono`) | Reference implementation; full filter/sort + `withCount` support. Both flavors emit byte-identical on-the-wire responses for the same metadata (same envelopes, same status codes, same filter operator parser), so consumers can pick the server framework that matches their runtime (Fastify for long-lived Node, Hono for Workers / Bun / edge). |
193193
| C# | shipped — `MetaObjects.Codegen` `RoutesGenerator` → ASP.NET Minimal API | `MapGet` / `MapPost` / `MapPut` / `MapDelete` mounted under `apiPrefix`; full CRUD. |
194-
| Java | shipped — `metaobjects-codegen-spring` `SpringControllerGenerator` + `SpringDtoGenerator` + `SpringRepositoryGenerator` → Spring `@RestController` (Spring Boot 3.x / Spring Web MVC) | One controller per writable entity (`source.rdb @kind="table"`); 5 CRUD endpoints (GET list / GET by id / POST / PATCH + PUT / DELETE); `?sort`, `?limit/?offset`, `?withCount=1` envelope, 404 + 400 envelopes per the contract. Java 21 record DTOs for request/response; a stubbed `<Entity>Repository` interface the consumer implements against their persistence layer (JPA / jOOQ / JDBC). Filter operators (`eq/ne/...`) deferred — see the module's `KNOWN_GAPS.md`. |
195-
| Kotlin | shipped — `metaobjects-codegen-kotlin` `KotlinSpringControllerGenerator` → Spring `@RestController` | One controller per writable entity (`source.rdb @kind="table"`); 5 CRUD endpoints (GET list / GET by id / POST / PATCH+PUT / DELETE); `?sort`, `?limit/?offset`, `?withCount=1` envelope, 404 + 400 envelopes per the contract. Filter operators (`eq/ne/...`) deferred. |
196-
| Python | shipped — `metaobjects.codegen.generators.router_generator` → FastAPI `APIRouter` | One router per writable entity (`source.rdb @kind="table"`); 5 CRUD endpoints (GET list / GET by id / POST / PATCH+PUT / DELETE); `?sort`, `?limit/?offset`, `?withCount=1` envelope, 404 + 400 envelopes per the contract. Consumer wires the repository via FastAPI `app.dependency_overrides`; the generator emits a `Protocol` interface so the persistence layer (SQLAlchemy / asyncpg / etc.) is the consumer's choice. Filter operators (`eq/ne/...`) deferred — see the module's `KNOWN_GAPS.md`. |
194+
| Java | shipped — `metaobjects-codegen-spring` `SpringControllerGenerator` + `SpringDtoGenerator` + `SpringRepositoryGenerator` → Spring `@RestController` (Spring Boot 3.x / Spring Web MVC) | One controller per writable entity (`source.rdb @kind="table"`); 5 CRUD endpoints (GET list / GET by id / POST / PATCH + PUT / DELETE); `?sort`, `?limit/?offset`, `?withCount=1` envelope, 404 + 400 envelopes per the contract. Java 21 record DTOs for request/response; a stubbed `<Entity>Repository` interface the consumer implements against their persistence layer (JPA / jOOQ / JDBC). Filter operators (`eq/ne/gt/gte/lt/lte/in/like/isNull`) ship via the generated `<Entity>FilterAllowlist` (`SpringFilterAllowlistGenerator`) + the runtime `FilterParser`, wired directly into the list handler. |
195+
| Kotlin | shipped — `metaobjects-codegen-kotlin` `KotlinSpringControllerGenerator` → Spring `@RestController` | One controller per writable entity (`source.rdb @kind="table"`); 5 CRUD endpoints (GET list / GET by id / POST / PATCH+PUT / DELETE); `?sort`, `?limit/?offset`, `?withCount=1` envelope, 404 + 400 envelopes per the contract. Filter operators ship via the generated `<Entity>FilterAllowlist` (`KotlinFilterAllowlistGenerator`) + an inline `parse<Entity>Filter` helper emitted in the controller. |
196+
| Python | shipped — `metaobjects.codegen.generators.router_generator` → FastAPI `APIRouter` | One router per writable entity (`source.rdb @kind="table"`); 5 CRUD endpoints (GET list / GET by id / POST / PATCH+PUT / DELETE); `?sort`, `?limit/?offset`, `?withCount=1` envelope, 404 + 400 envelopes per the contract. Consumer wires the repository via FastAPI `app.dependency_overrides`; the generator emits a `Protocol` interface so the persistence layer (SQLAlchemy / asyncpg / etc.) is the consumer's choice. Filter operators ship via the generated `<entity>_filter_allowlist.py` (`filter_allowlist_generator.py`) + the shared `filter_parser` helper, wired into the list handler. |
197197

198-
## Hand-writing a conforming controller
198+
## Hand-writing a conforming controller (if you outgrow the generated one)
199199

200-
While the planned route-codegen work matures, here is the minimum
201-
controller needed to make `useAuthors`, `useAuthor`, etc. work against
202-
each non-TS / non-C# backend. The shape is the same in every language —
203-
mount five (or six, if you want PUT) routes under `apiPrefix` that
204-
match the URL grammar above.
200+
Every port ships a route generator today (see the status table above) —
201+
you do not need to hand-write a controller to get a conforming API. This
202+
section is for the rare case where you need a shape the generator
203+
doesn't cover (a custom auth scheme, a framework the generator doesn't
204+
target, etc.) and want a hand-rolled controller that still speaks the
205+
contract. The shape is the same in every language — mount five (or six,
206+
if you want PUT) routes under `apiPrefix` that match the URL grammar
207+
above.
205208

206209
### Java — Spring `@RestController`
207210

@@ -303,20 +306,24 @@ into your framework's idiomatic query-builder, gated by the generated
303306

304307
## Future direction
305308

306-
Cross-port route codegen — for Java (Spring), Kotlin (Spring-Kotlin /
307-
Ktor), Python (FastAPI), and a browser-side Angular client — is planned
308-
but not yet specced. The planned FR will track:
309+
Cross-port route + filter codegen (Java, Kotlin, Python), the shared
310+
route-shape oracle (`fixtures/api-contract-conformance/`), and a
311+
browser-side Angular client have all shipped — see "Per-port route
312+
codegen status" and "Verified by" above, and
313+
[`docs/ports/typescript-client.md#angular-18`](../ports/typescript-client.md#angular-18).
309314

310-
- A shared route-shape oracle (analogous to the persistence-conformance
311-
corpus) covering `list` / `get` / `create` / `update` / `delete` +
312-
filter / sort / `withCount` + the error-response shape.
313-
- Per-port route generators emitting idiomatic controllers / routers
314-
against that oracle.
315-
- An Angular client (browser-side) that consumes the same generated
316-
hook surface as the React client.
315+
What's still genuinely open:
317316

318-
Until then, hand-written controllers per the templates above are
319-
expected, and they remain the conformance gate.
317+
- The five **TS-only filter extensions** (`?search=`, `filter[or]` /
318+
`filter[and]` nesting, leading-wildcard gating, the nesting-depth cap,
319+
and the `in`-list size cap) — see "TS-only filter extensions" above.
320+
None touch the metamodel vocabulary, so any of them can be promoted
321+
cross-port later as a purely additive, non-breaking change if real
322+
consumer demand shows up.
323+
- The `error` code vocabulary is not yet a hard cross-port invariant
324+
beyond `not_found` and the filter-parser error codes.
325+
326+
Neither item blocks adoption on any port today.
320327

321328
## Verified by
322329

@@ -337,15 +344,14 @@ envelope shape) is exercised by the cross-port corpus at
337344
port's runner spins up a real HTTP server hosting its emitted routes for the
338345
canonical `Author` entity, walks the scenarios, and asserts byte-shape
339346
identical responses against the cross-port `expect.body.*` vocabulary.
340-
TypeScript + Kotlin runners ship; Java / C# / Python runners are planned
341-
follow-ups. See [`docs/CONFORMANCE.md`](../CONFORMANCE.md) for per-port pass
342-
status.
343-
344-
Filter operator coverage (`eq` / `ne` / `gt` / `like` / `in` / `isNull`) is
345-
deferred from the API-contract corpus on purpose — backends defer those per
346-
their `KNOWN_GAPS.md`, so a scenario for `filter[name][like]=...` would fail
347-
on every port today. Filter-operator scenarios land alongside the per-port
348-
operator implementations.
347+
All five ports ship a runner — TypeScript, Java, Kotlin, C#, and Python
348+
each spin up their generated routes and pass every scenario. See
349+
[`docs/CONFORMANCE.md`](../CONFORMANCE.md) for per-port pass status.
350+
351+
Filter operator coverage (`eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `in` /
352+
`like` / `isNull`) is part of the corpus: 10 filter scenarios run on top of
353+
the 10 base CRUD scenarios, and all five ports satisfy all 20 today (see
354+
[`docs/CONFORMANCE.md`](../CONFORMANCE.md)).
349355

350356
## See also
351357

0 commit comments

Comments
 (0)