From e7230f9b955302292b423fa37be0a47ac0675f0c Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Tue, 27 Oct 2026 23:01:01 -0400 Subject: [PATCH 1/4] docs: keep-your-ORM paths, TypeScript-scoped three-way merge, baselines and known gaps per port --- AGENTS.md | 2 +- CHANGELOG.md | 4 +++ docs/features/codegen-data-shapes.md | 2 +- docs/features/migrations-and-drift.md | 2 +- docs/features/own-your-codegen.md | 2 +- docs/llms/llms-full.txt | 12 +++---- docs/llms/llms.txt | 2 +- docs/ports/csharp.md | 51 +++++++++++++++++++++++++-- docs/ports/java.md | 35 +++++++++++++++++- docs/ports/kotlin.md | 23 ++++++++++-- docs/ports/python.md | 19 ++++++++-- docs/ports/typescript.md | 46 +++++++++++++++++++++++- 12 files changed, 179 insertions(+), 21 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c122ad506..9ef0ff695 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ The metamodel is the **durable spine**; generated code is the **disposable artif The first four ship per-language today across the five ports (TS / C# / Java / Python / Kotlin), with cross-port conformance corpora verifying byte-identical behavior. The fifth ships its vocabulary, its `verify` checks and a `requirement-tests` generator in every port, and its authoring lint in TypeScript only. The sixth is content the other five compose, shipped as named opt-in artifacts: -1. **Codegen** *(reference helpers, not core — see "Core vs helpers" above; ejectable in every port)* — emit per-language code (Drizzle/Zod + Fastify for TS, EF Core + ASP.NET for C#, Spring REST + DTO + Repository for Java via `codegen-spring`, Pydantic + FastAPI for Python, KotlinPoet + Exposed + Spring for Kotlin via `codegen-kotlin`). Hand-edit-preserving regen via three-way merge. +1. **Codegen** *(reference helpers, not core — see "Core vs helpers" above; ejectable in every port)* — emit per-language code (Drizzle/Zod + Fastify for TS, EF Core + ASP.NET for C#, Spring REST + DTO + Repository for Java via `codegen-spring`, Pydantic + FastAPI for Python, KotlinPoet + Exposed + Spring for Kotlin via `codegen-kotlin`). Hand-edit-preserving regen via three-way merge (TypeScript only). 2. **Runtime metadata** — load metadata at runtime, drive behavior dynamically (CRUD, validation, relationships, dynamic admin UIs, LLM tool registration). On Kysely (TS), a DB-API 2 driver via ObjectManager (Python), modernized JDBC + Spring-tx via OMDB (Java), Exposed (Kotlin). **C# runtime metadata is on the roadmap** — its EF Core output is generated code, not a metadata-driven runtime; the runtime `MetaObjects` package depends only on YamlDotNet and ships no data access. 3. **Drift detection** — `meta verify` catches divergence between code and metadata (covers entity codegen, prompt templates, output parsers, schema). Quality-of-life on top of codegen + runtime. 4. **Prompt construction** — a prompt is code, not a string scattered across services. Declare a prompt's payload as a typed projection (payload bloat becomes a diff), keep its text external and provider-resolved, and render it deterministically: snapshot-testable, cache-stable (no whitespace change silently breaking exact-prefix prompt-cache hits), and drift-checked at build time so a renamed field can't degrade a prompt. Conformance-gated, so the guarantee holds in every language port. **Render + payload-VO codegen + `verify` + parser-on-receipt for a *responding* `template.prompt` — one carrying `@responseRef` (FR-006) — + the output-format prompt fragment & tolerant `extract` parser (FR-010) ship in all five ports today** (since 0.24.0 the whole inbound tier keys off `@responseRef`; a `template.output` is outbound-only and emits no parser — ADR-0052) — the library-side building blocks of the pillar are complete. The one remaining library-side piece is MCP exposure of declared prompts/tools (see `spec/roadmap.md`); the application-level consolidation (eval harness, end-to-end declared-prompt orchestration) and consumer adoption are exercised in adopter projects, not in this library repo. Designed in `docs/superpowers/specs/2026-05-22-fr-004-cross-language-prompt-construction-design.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md index f3bdf987e..eb1051c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ here.** ## [Unreleased] +### Docs + +- **Port docs and `llms-full.txt` now say what a fresh reader got wrong.** Each port page opens with its keep-your-ORM path (TypeScript `runtime: false`, Java and Kotlin records and data classes, sourceless C# POCOs as DTOs, Pydantic in Python); three-way merge is scoped to TypeScript everywhere it was claimed unscoped, with what the other ports do instead (committed hash manifest, refusal by name, eject); framework baselines are stated (Java 21 rather than the contradictory 17, Spring Boot 3.x with Boot 2 unsupported, .NET 8, Python 3.11, Node first and Bun second); and each port page carries its known gaps with absolute links. + ### Added - **Report export: `GET /?format=csv|json|xml` on a served report, in all five ports.** diff --git a/docs/features/codegen-data-shapes.md b/docs/features/codegen-data-shapes.md index 609b3d354..0da632291 100644 --- a/docs/features/codegen-data-shapes.md +++ b/docs/features/codegen-data-shapes.md @@ -14,7 +14,7 @@ MetaObjects **major** release; deprecations are announced before removal. |---|---|---| | **Data extraction** | per-port walker (TS / C# / Python / Java / Kotlin) | typed data dict | | **Template rendering** | shared Mustache via `@metaobjectsdev/render` | Mustache spec | -| **File emission** | the runner + overwrite policy | three-way merge | +| **File emission** | the runner + overwrite policy | hash manifest (refuse an edited file); three-way merge in TypeScript only | The middle layer is **language-agnostic**: the same Mustache template that renders `.md` from TypeScript walks identically from C# or Python. diff --git a/docs/features/migrations-and-drift.md b/docs/features/migrations-and-drift.md index df7318411..20e187339 100644 --- a/docs/features/migrations-and-drift.md +++ b/docs/features/migrations-and-drift.md @@ -15,7 +15,7 @@ There are **7 drift sources**, and the toolchain has a guard for each. | **Code-vs-API-doc** | Cross-port codegen from the same metadata. | Build time | | **DB-vs-metadata** | `meta verify --db` (TS CLI) — introspects the live DB and fails if it has drifted from metadata. Includes modeled projection **view bodies** (a changed `CREATE VIEW` is drift, reported with what differs: the first differing excerpt of the text on SQLite/D1, a fingerprint mismatch on Postgres). A view `meta migrate` drops and recreates only because the migration alters a table it reads, with its definition unchanged, is not drift and is not reported; a hand-authored *unmodeled* view is unmanaged and never flagged. A schema concern owned by the Node toolchain regardless of server language; on the JVM ports the runtime auto-create/validator path was removed (ADR-0015) and the `metaobjects:verify` Maven goal is not available. Cloudflare D1 has no client wire protocol, so it can't go through `--db`'s Kysely-driver introspection — use `meta verify --dialect d1 [--d1 ] [--remote]` instead (the same wrangler-shelled-out path `meta migrate --dialect d1` uses); `--remote` is required to check the *deployed* database, not the local `wrangler dev` shadow copy. Pointing `--db file:` at wrangler's local D1 state directory (`.wrangler/state/**/d1/**`) still runs, but only verifies that local copy — `verify` warns when it detects this. | CI on every PR | | **Migration-vs-metadata** | The Node `meta migrate` emits migrations FROM metadata diffs — they cannot drift from metadata by construction. Schema migrations for **every** port are owned by this Node toolchain (`@metaobjectsdev/cli migrate`, ADR-0015); the C# and Python migrate surfaces were removed. | Build time | -| **Generated-edited** | `@generated` headers in emitted code + three-way merge that preserves hand-edits inside non-generated regions. | Code review | +| **Generated-edited** | `@generated` headers in emitted code + a committed hash manifest that refuses an edited file by name; in TypeScript only, a three-way merge also preserves hand-edits on the machine that generated the file. | Code review | | **Prompt-vs-payload** | FR-004 `Renderer.verify` parses `{{...}}` references in templates and checks each one exists on the payload VO. | Build time + runtime | | **Generated-vs-runtime** | Kotlin / Java `MetadataStartupValidator` (from Spring `ApplicationReadyEvent`) re-loads metadata at startup and asserts the generated table objects match. | App startup | diff --git a/docs/features/own-your-codegen.md b/docs/features/own-your-codegen.md index 4c996f0e4..72514db2c 100644 --- a/docs/features/own-your-codegen.md +++ b/docs/features/own-your-codegen.md @@ -430,7 +430,7 @@ survive depends on what the toolchain can see, and it is worth knowing which cas are in: - **On the machine that generated the file**, `.metaobjects/.gen-state/` holds a full - snapshot of what was last written, and `meta gen` does a real three-way merge: your + snapshot of what was last written, and the TypeScript `meta gen` does a real three-way merge (C#, Python, Java and Kotlin do not merge; see below): your edit and the new generated content are combined, and you only see a conflict when they touch the same lines. - **Anywhere else** — a fresh clone, a teammate's checkout, CI — the snapshot bodies are diff --git a/docs/llms/llms-full.txt b/docs/llms/llms-full.txt index 73041ee5e..8a3f52e18 100644 --- a/docs/llms/llms-full.txt +++ b/docs/llms/llms-full.txt @@ -57,13 +57,13 @@ Reference generators you own, ejectable in every port. They emit per-language co **What is guaranteed, and what is yours** (ADR-0034 Amendment 3). The core is the promise: the metamodel, loader, canonical format and registry; runtime metadata access (the `ObjectManager`, not the HTTP adapters that mount it); schema migrations (`meta migrate`); the drift gates (`meta verify`); prompt render and the reply parser. Every generator that writes application code into the repo is a reference helper — a starting point that compiles and passes its reference fixtures, which the adopter copies with `meta eject` and owns. A defect in the core is a MetaObjects bug; a defect in a generator is fixed in the reference, and the adopter's copy is theirs. The test is mechanical: what the tool guarantees is core; what it writes into your repo is a helper. -- TypeScript: Drizzle + Zod + Fastify integration (and React / TanStack on the client side) -- Java: Spring REST + DTO + repository interfaces (the persistence impl is consumer-supplied) via `codegen-spring` -- Kotlin: KotlinPoet + Exposed + Spring via `codegen-kotlin` -- C#: EF Core + ASP.NET -- Python: Pydantic + FastAPI +- TypeScript: Drizzle + Zod + Fastify integration (and React / TanStack on the client side). **Keep your ORM:** a target with `runtime: false` makes the `entity` generator write Zod schemas and inferred types only, with no Drizzle and no `runtime-ts` import +- Java: Spring REST + DTO + repository interfaces (the persistence impl is consumer-supplied) via `codegen-spring`. **Keep your ORM:** `SpringDtoGenerator` and `SpringValueObjectGenerator` write records (jakarta validation) and your JPA entity stays hand-written. Baseline Java 21, Spring Boot 3.x (Boot 2 is not supported) +- Kotlin: KotlinPoet + Exposed + Spring via `codegen-kotlin`. **Keep your ORM:** `KotlinEntityGenerator` alone writes plain data classes; JVM 21, Spring Boot 3.x +- C#: EF Core + ASP.NET (.NET 8). **Keep your EF model:** an object declared with no `source.rdb` generates a POCO with DataAnnotations and no EF mapping, a contract separate from your entities +- Python: Pydantic + FastAPI (Python 3.11+). **Keep your ORM:** the `entity` generator writes Pydantic models with no ORM of any kind -Hand-edit-preserving regeneration uses a three-way merge so engineers can extend generated entities without losing their work when metadata changes. M:N relationship codegen (FR-018) ships in all five ports: entity navigation, idiomatic ORM wiring (Drizzle m2m / EF Core `UsingEntity` / Spring repo interface / Exposed / Pydantic), and REST traversal (`GET //{id}/`). +Hand-edit-preserving regeneration is **TypeScript-only**: `meta gen` three-way-merges your edits into regenerated output on the machine that generated the file. C#, Python, Java and Kotlin do not merge. Each decides from a committed hash manifest (`.metaobjects/.gen-state/.hashes.json`): a file that still matches what the generator wrote is overwritten, an edited one is refused by name (Java and Kotlin can be told to replace it with `handEdits=overwrite`), and `eject` hands you the generator to own instead. M:N relationship codegen (FR-018) ships in all five ports: entity navigation, idiomatic ORM wiring (Drizzle m2m / EF Core `UsingEntity` / Spring repo interface / Exposed / Pydantic), and REST traversal (`GET //{id}/`). ### Runtime metadata diff --git a/docs/llms/llms.txt b/docs/llms/llms.txt index 5dd6359be..cd3ea282b 100644 --- a/docs/llms/llms.txt +++ b/docs/llms/llms.txt @@ -50,7 +50,7 @@ MetaObjects pillars are capabilities of the same metadata spine, not separate pr - **Requirements and testing** — vocabulary, the `verify` checks and a `requirement-tests` generator in all five ports; the authoring lint is TypeScript-only; dogfooded on maintainer-owned projects, no outside adopter yet. - **Libraries** — since 1.0.4, in all five ports: `ai` is `stable`, `iam` is `preview` (the same labels `meta gen --list` prints). -- **Codegen** — reference generators you own (ejectable in every port) that emit per-language code from a single metadata model. Drizzle + Zod + Fastify (TypeScript), Spring REST + DTO + repository interfaces (the persistence impl is consumer-supplied) (Java via `codegen-spring`), KotlinPoet + Exposed + Spring (Kotlin via `codegen-kotlin`), EF Core + ASP.NET (C#), Pydantic + FastAPI (Python). Hand-edit-preserving regeneration via three-way merge. Includes M:N relationship codegen (FR-018) in all five ports — entity navigation, idiomatic ORM wiring, and REST traversal (`GET //{id}/`). +- **Codegen** — reference generators you own (ejectable in every port) that emit per-language code from a single metadata model. Drizzle + Zod + Fastify (TypeScript), Spring REST + DTO + repository interfaces (the persistence impl is consumer-supplied) (Java via `codegen-spring`), KotlinPoet + Exposed + Spring (Kotlin via `codegen-kotlin`), EF Core + ASP.NET (C#), Pydantic + FastAPI (Python). Hand-edit-preserving regeneration via three-way merge is TypeScript-only; the other ports refuse an edited generated file by name and let you eject the generator to own it. Keep your ORM: contract-only output (TypeScript `runtime: false`: Zod and types; Java and Kotlin: records and data classes; C#: sourceless POCOs; Python: Pydantic) leaves your Prisma, JPA or EF Core models untouched. Includes M:N relationship codegen (FR-018) in all five ports — entity navigation, idiomatic ORM wiring, and REST traversal (`GET //{id}/`). - **Runtime metadata** — load metadata at runtime and drive behavior dynamically: CRUD, validation, relationships, dynamic admin UIs. (Typed tool payloads are declared today; MCP exposure of tools is on the roadmap.) Kysely (TS), a DB-API 2 driver (pg8000 / psycopg) via ObjectManager (Python), modernized JDBC + Spring-tx via OMDB (Java), Exposed (Kotlin); C# runtime metadata is on the roadmap (its EF Core output is generated code, not a metadata-driven runtime). Runtime queries return native in-process types (ADR-0019); wire canonicalization happens only at the serialization boundary. - **Drift detection** — catch divergence between generated code and metadata before it ships. `verify` is one verb with explicit subverbs (ADR-0021): `verify --codegen` (regen-and-diff against committed output), `verify --templates` (prompt `{{field}}` ↔ payload-VO drift), and `verify --db` (live-DB schema drift, Node `meta` only). Surfaces drift as build-time breakage rather than a production incident. - **Prompt construction** — treat LLM prompts as governed metadata instead of strings scattered across services. A typed payload declared as a projection (so payload bloat and token cost are a diff, not a mystery), external provider-resolved prompt text, and a logic-less Mustache engine that renders deterministically: snapshot-testable in CI, byte-stable so an exact-prefix prompt-cache hit doesn't break on a stray whitespace, and drift-checked at build time so a renamed field can't silently degrade a prompt. Conformance-gated, so the guarantee holds in every language port. Render + payload-VO codegen + `verify` + `template.output` parser-on-receipt (FR-006) + the output-format prompt fragment & tolerant `extract` parser (FR-010/FR-011) ship in all five ports today. diff --git a/docs/ports/csharp.md b/docs/ports/csharp.md index bda534030..fc81d0394 100644 --- a/docs/ports/csharp.md +++ b/docs/ports/csharp.md @@ -2,7 +2,43 @@ A first-class full-stack target on .NET 8. Loader + conformance + EF Core codegen + render engine + the `meta` CLI all ship. Targets EF Core + ASP.NET Core + -Postgres + Npgsql. +Postgres + Npgsql. The baseline is **.NET 8** (`net8.0`); the tool and the packages are not +built for earlier runtimes. + +## Regeneration and hand edits + +`dotnet meta gen` has no three-way merge (that is TypeScript-only). It decides per file from a +committed hash manifest, `.metaobjects/.gen-state/.hashes.json`: a file that still matches what +the generator wrote is overwritten, and an edited one is refused by name. To change what a +generator emits, `dotnet meta eject ` copies it into `codegen/generators/` for you to own. +Commit the manifest. + +## Keep your EF Core model: contracts as plain DTOs + +Contracts and entities are not forced into one type. Declare an object with **no +`source.rdb`** (a *sourceless* object) and `EntityGenerator` writes a POCO with DataAnnotations +and **no EF mapping**: no `[Table]`, no `DbSet<>`, no `AppDbContext` entry, no CRUD routes, no +filter allowlist. A sourced entity gets `[Table]`/`[Key]`/`[Column]` and the full EF Core +surface. So the same model can hold sourceless request/response contracts that you hand to your +own controllers and sourced entities where you want MetaObjects to own the mapping; your +existing hand-written EF entities are not touched. + +A declaration with no `source.rdb` (this one is a plain DTO POCO): + +```json +{ "object.entity": { + "name": "AuthorSummary", + "children": [ + { "field.long": { "name": "id" } }, + { "field.string": { "name": "name", "@required": true, "@maxLength": 200 } }, + { "identity.primary": { "@fields": "id" } } + ] +}} +``` + +Wire only the generators you want in `codegen/Generators.cs`. Note that the generated CRUD +routes use the *entity* POCO, as described under [Use](#use); the DTO shape above is for +objects you keep sourceless. ## Install @@ -720,8 +756,17 @@ ship too — the generated `FilterAllowlist` (`FilterAllowlistGenerator` feeds `FilterParser.Parse` + `EfCoreFilterDispatch.ApplyFilter`, both wired directly into the generated list handler. A read-only projection (`source.rdb @kind: view`) and a view-backed report get the same filter and sort -on their list route, against an allowlist of their own fields. Remaining gaps are in -[`server/csharp/MetaObjects.Codegen/Generators/KNOWN_GAPS.md`](../../server/csharp/MetaObjects.Codegen/Generators/KNOWN_GAPS.md). +on their list route, against an allowlist of their own fields. + +**Known gaps** (full list with workarounds in +[KNOWN_GAPS.md](https://github.com/metaobjectsdev/metaobjects/blob/main/server/csharp/MetaObjects.Codegen/Generators/KNOWN_GAPS.md)): +no `filter[or]` / `filter[and]` boolean grouping and a single sort key only (both outside the +cross-port contract today); the `View` / `Views` names are reserved for a +write-through entity's generated read model; an `EmitMappedClass` override does not reach that +read model; M:N traversal on a write-through entity is not routed through the replica view; the +ordered-comparison filter fallback in `EfCoreFilterDispatch` needs a hand-written typed +dispatcher for some column types; there is no metadata-driven runtime tier (EF Core output is +generated code), and no `migrate` surface (schema is TypeScript-owned). ## Capability snapshot diff --git a/docs/ports/java.md b/docs/ports/java.md index 27369b2e3..563466ac1 100644 --- a/docs/ports/java.md +++ b/docs/ports/java.md @@ -4,17 +4,38 @@ The Java port targets Spring-Boot consumers on Maven. It ships the full metamode + loader + conformance + OMDB runtime persistence engine + the FR-004 render engine, plus the `metaobjects-maven-plugin` for build-time codegen (`mvn metaobjects:generate` / `metaobjects:editor`). +**Baselines.** Java 21 (the published artifacts are compiled with `--release 21`, so they do +not load on 17) and Spring Boot 3.x (the generated code uses `jakarta.*` and Spring Web MVC 6; +Spring Boot 2 and `javax.*` are not supported). + +**Keep your ORM (JPA, Hibernate, jOOQ, JDBC).** You do not have to adopt the OMDB runtime or +replace your entities. `SpringDtoGenerator` (`dto`) writes a record per concrete entity or +projection, whatever its source, with jakarta validation annotations, and +`SpringValueObjectGenerator` (`value-object`) writes a record per value object. Your JPA entity +stays hand-written, and the generated `Repository` is an interface *you* implement +with Spring Data JPA, jOOQ or plain JDBC. Wire only the generators you want in +``. + Schema migrations are owned by the TypeScript toolchain (`@metaobjectsdev/cli migrate`); the Java diff-and-converge migration engine and its `meta:migrate` / live-DB-drift `metaobjects:verify` Maven goals were removed. Per ADR-0015 the OMDB runtime auto-create path was also removed — OMDB is pure data-access (CRUD/query/codec/transactions). Prompt / template drift is still checked via the `metaobjects-render` `Verify` API. +## Regeneration and hand edits + +There is no three-way merge on the JVM (that is TypeScript-only). `mvn metaobjects:generate` +records each file it writes in `.metaobjects/.gen-state/.hashes.json`: a file that still matches +is overwritten, and an edited one is refused by name (`keep`, the default) +or replaced (`overwrite`). `mvn metaobjects:eject -Dnames=` copies a +generator into your repo to own. Commit the manifest. Details: +[Own your codegen](../features/own-your-codegen.md). + ## Install Set `${metaobjects.version}` to the current Maven Central release (`8.1.4`) — both the dependency and plugin blocks below resolve it from one `` entry. The -generated records need Java 17 or newer, so set the compiler level here too (Maven +artifacts and the generated records need Java 21, so set the compiler level here too (Maven defaults to Java 8, where `mvn compile` fails with "records are not supported in -source 8"): ```xml @@ -848,6 +869,18 @@ in the consumer's `@Configuration`). The generated controllers do not emit `@CrossOrigin` — adding it cross-port would require a CORS-policy configuration model that has not yet been specced. +## Known gaps + +The full list is in +[KNOWN_GAPS.md](https://github.com/metaobjectsdev/metaobjects/blob/main/server/java/codegen-spring/src/main/java/com/metaobjects/generator/spring/KNOWN_GAPS.md). +In short: the generated controller assumes a single-field `Long` primary key (a `UUID` key +generates but the `@PathVariable Long id` typing needs a hand edit; composite keys are not +supported), request and response share one `Dto` (no separate insert or update types), +generated output parsers throw on bad input (there is no try-style variant) and need Jackson on +the runtime classpath, and no stored-procedure callable generator exists (FR-015 ships in +TypeScript, C# and Kotlin only). OMDB is data access only; schema migrations are owned by the +Node `meta` CLI. + ## Capability snapshot | Feature | Status | diff --git a/docs/ports/kotlin.md b/docs/ports/kotlin.md index 513cd03ae..32d403d44 100644 --- a/docs/ports/kotlin.md +++ b/docs/ports/kotlin.md @@ -7,12 +7,28 @@ runners are all Java; Kotlin emits idiomatic Kotlin (`data class`, Exposed `Table` objects, extension-fn relationship helpers, Spring `@Configuration` wiring) via KotlinPoet. +**Baselines.** JVM 21 (the published artifacts target JVM 21), Kotlin 2.0, and Spring Boot 3.x +for the generated controllers (`jakarta.*`; Spring Boot 2 is not supported). + +**Keep your ORM.** `KotlinEntityGenerator` (with `KotlinValidatorGenerator`) writes plain +Jackson-compatible data classes per entity and value object. Wire it without the Exposed table +generator and your own JPA, Exposed or JDBC mapping stays hand-written beside the generated +classes. + Two modules: - **`metaobjects-codegen-kotlin`** — 16 KotlinPoet-based generators. - **`metaobjects-metadata-ktx`** — thin Kotlin facade over the Java loader + render engine for idiomatic Kotlin runtime use. +## Regeneration and hand edits + +There is no three-way merge on the JVM (that is TypeScript-only). Kotlin runs through the same +`mvn metaobjects:generate` write path as Java: a file that still matches the committed hash +manifest (`.metaobjects/.gen-state/.hashes.json`) is overwritten, and an edited one is refused by +name (or replaced with `overwrite`). `mvn metaobjects:eject -Dnames=` +copies a generator into your repo to own. + ## Install Every version below comes from one `` entry. Set the Java level (Maven defaults to @@ -803,8 +819,11 @@ dependencies { No kotlinx-serialization plugin is needed for the template tier: the value objects' data classes carry no `@Serializable` (it was decorative — no build enabled the compiler plugin it needs). See -[`codegen-kotlin/KNOWN_GAPS.md`](../../server/java/codegen-kotlin/KNOWN_GAPS.md) -for the full consumer-wiring contract. Cross-port design is at +[KNOWN_GAPS.md](https://github.com/metaobjectsdev/metaobjects/blob/main/server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/KNOWN_GAPS.md) +for the full consumer-wiring contract and the known gaps (single-field `Long` primary keys +only, one `` data class for request and response, composite-FK relationships not +emitted, `ObjectField` skipped in the sort allowlist, and a Jackson dependency for typed jsonb +and parsers). Cross-port design is at [ADR-0010](../../spec/decisions/ADR-0010-template-output-parser-codegen.md); the feature reference is at [`features/templates-and-payloads.md`](../features/templates-and-payloads.md#response-parsing-fr-006). diff --git a/docs/ports/python.md b/docs/ports/python.md index 64bdd31a7..e6df96be3 100644 --- a/docs/ports/python.md +++ b/docs/ports/python.md @@ -3,10 +3,19 @@ Targets Python 3.11+ on the FastAPI / Pydantic stack. Ships the metadata loader (canonical JSON + sigil-free YAML), conformance runner over the shared corpora, the FR-004 render engine, and the entity / payload / router -codegen. Schema migrations are owned by the Node `meta` CLI (ADR-0015) — the +codegen. **Keep your ORM:** the `entity` generator writes Pydantic models with no ORM of any +kind, so your SQLAlchemy or Django models stay hand-written beside them. Schema migrations are owned by the Node `meta` CLI (ADR-0015) — the Python port has no `migrate` command by design; it consumes the canonical `schema.postgres.sql` artifact verbatim. +## Regeneration and hand edits + +`metaobjects gen` has no three-way merge (that is TypeScript-only). It decides per file from a +committed hash manifest, `.metaobjects/.gen-state/.hashes.json`: a file that still matches what +the generator wrote is overwritten, and an edited one is refused by name. To change what a +generator emits, `metaobjects eject ` copies it into `codegen/generators/` for you to own. +Commit the manifest. + ## Install Requires **Python 3.11+**. Published to PyPI as **`metaobjects`**: @@ -267,8 +276,12 @@ The router conforms to the cross-port API contract a static per-entity allowlist (HTTP 400 envelope on unknown field); 404 envelope is `{"error": "not_found"}`. Filter operators (`eq` / `ne` / …) **do** ship — a per-entity filter allowlist is generated and the router parses `filter[field][op]` -into typed predicates. Remaining gaps are tracked in -[`server/python/src/metaobjects/codegen/KNOWN_GAPS.md`](../../server/python/src/metaobjects/codegen/KNOWN_GAPS.md). +into typed predicates. Remaining gaps are listed in +[KNOWN_GAPS.md](https://github.com/metaobjectsdev/metaobjects/blob/main/server/python/src/metaobjects/codegen/KNOWN_GAPS.md); in short: the generated +router assumes a single-field `int` primary key (other types generate but need a hand edit to the +`: int` typing), the router's request and response types are `dict[str, Any]` rather than +per-entity Pydantic models, and there is no stored-procedure callable generator (FR-015 ships in +TypeScript, C# and Kotlin only). Wire the router into your consumer FastAPI app: diff --git a/docs/ports/typescript.md b/docs/ports/typescript.md index 6a6f86b7e..e947e3d2b 100644 --- a/docs/ports/typescript.md +++ b/docs/ports/typescript.md @@ -2,7 +2,38 @@ The reference implementation. Published to npm at `1.1.4` as 14 `@metaobjectsdev/*` packages on the `latest` tag. Targets Node-compatible -runtimes; Bun-first dev workflow. +runtimes: the generated code and the CLI run on stock Node (via `tsx` or after a `tsc` +build), and Bun is simply the fastest dev workflow in this repo. + +## Regeneration and hand edits + +Three-way merge is a TypeScript feature: on the machine that generated a file, `meta gen` +merges your edits into regenerated output. On a fresh clone or in CI the snapshot bodies are +not present, so an edited file is refused by name rather than merged (commit +`.metaobjects/.gen-state/.hashes.json`). The other ports do not merge at all. + +## Keep your ORM (Prisma, TypeORM, Drizzle you already have) + +Adopting the TypeScript port does not mean replacing your data layer. The `entity` +generator (`entityFile()`) writes a Drizzle table binding only for a target that has server +runtime bindings, which is the default. Point it at a **contract-only target** +(`runtime: false`) and it writes Zod schemas and inferred TypeScript types, with no +`drizzle-orm` and no `@metaobjectsdev/runtime-ts` import, for entities, value objects and +projections alike. Your Prisma (or other ORM) models stay hand-written beside them. + +```ts +export default defineConfig({ + outDir: "src/generated", + dialect: "postgres", + targets: { contract: { outDir: "src/contract", runtime: false } }, + generators: [entityFile({ target: "contract" })], // Zod + types only +}); +``` + +A model of only value objects and sourceless projections needs no `dialect`. A sourceless +object generates its type and nothing else: no table, no migration, no routes. See +[Own your codegen](../features/own-your-codegen.md) for ejecting a generator, and the +[fit assessment](https://metaobjects.dev/assess.md) for deciding which pieces to adopt. ## Install @@ -838,6 +869,19 @@ See [Generated requirement tests and witnesses](../features/requirements.md#gene project's verify script or pre-push hook. See [Requiring implementers](../features/requirements.md#requiring-implementers). +## Known gaps + +TypeScript is the reference port and has no separate gaps file; the capability snapshot below is +its list. The limits worth knowing before you commit: `meta migrate` and `meta verify --db` +cover Postgres, SQLite and D1 only (MySQL gets codegen and runtime, not migrations). Oracle and +SQL Server are not supported targets: `meta migrate` will not manage their schema, and the generated +Drizzle tier does not target them. The contract-only output of the other ports (Java records, sourceless +C# POCOs, Pydantic models) never touches the database, so a team on Oracle or SQL Server can +still use it, and manages its schema itself. and hand edits survive regeneration through the +three-way merge only on the machine that generated the file. The other ports' gaps are listed on +their own pages: [C#](csharp.md), [Java](java.md#known-gaps), +[Kotlin](kotlin.md), [Python](python.md). + ## Capability snapshot | Feature | Status | From 26472f0ce83a822e86773ac02d2da64e330ae6b7 Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Tue, 27 Oct 2026 23:02:59 -0400 Subject: [PATCH 2/4] docs: name db-context, exposed-table and Pydantic v2 in the keep-your-ORM notes --- docs/ports/csharp.md | 2 +- docs/ports/kotlin.md | 3 +-- docs/ports/python.md | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/ports/csharp.md b/docs/ports/csharp.md index fc81d0394..6fefbd874 100644 --- a/docs/ports/csharp.md +++ b/docs/ports/csharp.md @@ -36,7 +36,7 @@ A declaration with no `source.rdb` (this one is a plain DTO POCO): }} ``` -Wire only the generators you want in `codegen/Generators.cs`. Note that the generated CRUD +Wire only the generators you want in `codegen/Generators.cs`; leave `db-context` out if you keep your own `DbContext`, since the generated `AppDbContext` emits Postgres column types (`uuid`, `jsonb`, `timestamptz`) today. Note that the generated CRUD routes use the *entity* POCO, as described under [Use](#use); the DTO shape above is for objects you keep sourceless. diff --git a/docs/ports/kotlin.md b/docs/ports/kotlin.md index 32d403d44..a60159907 100644 --- a/docs/ports/kotlin.md +++ b/docs/ports/kotlin.md @@ -11,8 +11,7 @@ wiring) via KotlinPoet. for the generated controllers (`jakarta.*`; Spring Boot 2 is not supported). **Keep your ORM.** `KotlinEntityGenerator` (with `KotlinValidatorGenerator`) writes plain -Jackson-compatible data classes per entity and value object. Wire it without the Exposed table -generator and your own JPA, Exposed or JDBC mapping stays hand-written beside the generated +Jackson-compatible data classes per entity and value object. Wire `entity` without `exposed-table` and your own JPA, Exposed or JDBC mapping stays hand-written beside the generated classes. Two modules: diff --git a/docs/ports/python.md b/docs/ports/python.md index e6df96be3..d389ac0ef 100644 --- a/docs/ports/python.md +++ b/docs/ports/python.md @@ -3,7 +3,7 @@ Targets Python 3.11+ on the FastAPI / Pydantic stack. Ships the metadata loader (canonical JSON + sigil-free YAML), conformance runner over the shared corpora, the FR-004 render engine, and the entity / payload / router -codegen. **Keep your ORM:** the `entity` generator writes Pydantic models with no ORM of any +codegen. **Keep your ORM:** the `entity` generator writes Pydantic v2 models with no ORM of any kind, so your SQLAlchemy or Django models stay hand-written beside them. Schema migrations are owned by the Node `meta` CLI (ADR-0015) — the Python port has no `migrate` command by design; it consumes the canonical `schema.postgres.sql` artifact verbatim. From 94aafe71d691892a06d3b78434e5bafca76182bb Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Tue, 27 Oct 2026 23:06:04 -0400 Subject: [PATCH 3/4] no-mistakes(review): apply reviewer inline fixes --- docs/ports/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ports/typescript.md b/docs/ports/typescript.md index e947e3d2b..b7d4eb876 100644 --- a/docs/ports/typescript.md +++ b/docs/ports/typescript.md @@ -877,7 +877,7 @@ cover Postgres, SQLite and D1 only (MySQL gets codegen and runtime, not migratio SQL Server are not supported targets: `meta migrate` will not manage their schema, and the generated Drizzle tier does not target them. The contract-only output of the other ports (Java records, sourceless C# POCOs, Pydantic models) never touches the database, so a team on Oracle or SQL Server can -still use it, and manages its schema itself. and hand edits survive regeneration through the +still use it, and manages its schema itself. Hand edits survive regeneration through the three-way merge only on the machine that generated the file. The other ports' gaps are listed on their own pages: [C#](csharp.md), [Java](java.md#known-gaps), [Kotlin](kotlin.md), [Python](python.md). From 36cf2723989fb21d75e79dd5b46d48973ceed4a3 Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Wed, 28 Oct 2026 00:19:12 -0400 Subject: [PATCH 4/4] no-mistakes(document): Remove stale "no 3-way merge" claim from CLI README --- server/typescript/packages/cli/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/server/typescript/packages/cli/README.md b/server/typescript/packages/cli/README.md index e75834cce..c199c67b7 100644 --- a/server/typescript/packages/cli/README.md +++ b/server/typescript/packages/cli/README.md @@ -401,7 +401,6 @@ See `.metaobjects/AGENTS.md` (scaffolded by `meta init`) for the metaobjects met ## Not yet shipped - `meta gen --watch` (dropped) — re-run on demand -- True 3-way merge in `meta gen` — codegen-ts has overwrite/skip-existing only - Module-reference DB connections — URL-only - Reified SDK APIs for adding/promoting descriptive records — hand-edit JSON for now