Skip to content

Commit aa08dfe

Browse files
dmealingclaude
andcommitted
docs(ports): fix C#/Java/Kotlin quickstart inaccuracies (audit-driven)
The launch review only pressure-tested TS+Python; auditing the other three ports found the same class of defects (Java worst — a fabricated runtime example like Python's). All fixes verified against generator/CLI/render source. Java (docs/ports/java.md): Configure block cited a nonexistent JavaPojoGenerator → the real Spring* generators; the OMDB "Use" example was fabricated end-to-end (ObjectManagerDb/.builder()/persist/getObjectsBy + a never-generated Author POJO) → real connection-first ObjectManagerDB API; FR-004 Renderer.render is an instance method + RenderRequest is a record (no builder); Verify.verify/Renderer.verify don't exist → Verify.check; mvn goal prose meta:gen → metaobjects:generate; added the ${metaobjects.version}=7.11.3 note. C# (docs/ports/csharp.md): `dotnet meta --help` is broken → `dotnet meta`; removed the fictional ForgeTypesProvider + fixed CoreTypes.CoreTypesProvider/namespace; "record per entity" → class; RenderRequest object-initializer; real .g.cs filenames; /api/author → /api/authors; added the EF Core + Npgsql consumer-dep note. Kotlin (docs/ports/kotlin.md): generator count 7/9 (contradictory) → the real 14 (added the 5 missing rows); entity generator also emits object.projection; the Use example aligned to real emitted shape (nullable auto-PK, jakarta validation attrs, no @serializable on entities); stale "122 tests" softened. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XeGSV3StPCcJGZNNJ4ZfAb
1 parent 253188c commit aa08dfe

3 files changed

Lines changed: 125 additions & 58 deletions

File tree

docs/ports/csharp.md

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Install the CLI as a .NET tool:
2222

2323
```bash
2424
dotnet tool install --global MetaObjects.Cli
25-
dotnet meta --help
25+
dotnet meta # bare invocation prints the usage banner
2626
```
2727

2828
The C# CLI is invoked as `dotnet meta` (command `dotnet-meta`), or run directly
@@ -59,12 +59,11 @@ If your app needs a metamodel subtype the core doesn't ship, declare an
5959
`IMetaDataTypeProvider` and compose it into the registry before loading:
6060

6161
```csharp
62-
using MetaObjects.Metadata;
62+
using MetaObjects;
6363
using MetaObjects.Loader;
6464

6565
var registry = Provider.ComposeRegistry(new IMetaDataTypeProvider[] {
66-
CoreTypesProvider.Instance,
67-
ForgeTypesProvider.Instance,
66+
CoreTypes.CoreTypesProvider,
6867
yourProvider, // adds your custom subtype/attrs
6968
});
7069

@@ -99,11 +98,13 @@ Schema migrations are owned by the Node `meta` CLI (ADR-0015) — the C# CLI is
9998

10099
The codegen emits:
101100

102-
- `Author.cs`record per entity.
103-
- `AppDbContext.cs``DbSet<Author>`, projection `.ToView()`, `@storage` owned
101+
- `Author.g.cs`class per entity (a mutable attributed POCO, not a record).
102+
- `AppDbContext.g.cs``DbSet<Author>`, projection `.ToView()`, `@storage` owned
104103
types via `OwnsOne` (single) / `OwnsMany(...).ToJson(...)` (`@isArray` array-of-VO),
105104
enum-as-string via `HasConversion<string>()`.
106-
- `Author.routes.cs` — CRUD minimal-API endpoints.
105+
- `AuthorRoutes.g.cs` — CRUD minimal-API endpoints.
106+
- `AuthorFilterAllowlist.g.cs` — the server-side filter/sort allowlist feeding the
107+
generated list handler.
107108

108109
## Use
109110

@@ -118,12 +119,18 @@ builder.Services.AddDbContext<AppDbContext>(opts =>
118119

119120
var app = builder.Build();
120121

121-
app.MapAuthorRoutes(); // generated — GET/POST/PUT/DELETE on /api/author
122+
app.MapAuthorRoutes(); // generated — GET/POST/PUT/DELETE on /api/authors
122123
app.Run();
123124
```
124125

125126
EF Core does the rest. The runtime has no MetaObjects dependency.
126127

128+
**Consumer dependencies.** The generated `AppDbContext` and the `Program.cs`
129+
wiring above use EF Core (`AddDbContext`, `DbContext`, `UseNpgsql`), which
130+
MetaObjects does not pull in for you — add the two EF Core NuGet packages to
131+
the consuming app: `Microsoft.EntityFrameworkCore` and
132+
`Npgsql.EntityFrameworkCore.PostgreSQL`.
133+
127134
```csharp
128135
// Optional handwritten service over the generated DbContext
129136
public class AuthorService(AppDbContext db)
@@ -151,11 +158,12 @@ var payload = new WelcomePayload(
151158
PostCount: 12,
152159
Posts: new[] { new PostSummary("Hello") });
153160

154-
string output = Renderer.Render(new RenderRequest(
155-
Ref: "lobby/welcome",
156-
Payload: payload,
157-
Provider: provider,
158-
Format: "xml"));
161+
string output = Renderer.Render(new RenderRequest {
162+
Ref = "lobby/welcome",
163+
Payload = payload,
164+
Provider = provider,
165+
Format = "xml",
166+
});
159167
```
160168

161169
`Verify` in `MetaObjects.Render` drift-checks every `template.*` against its

docs/ports/java.md

Lines changed: 83 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
The Java port targets Spring-Boot consumers on Maven. It ships the full metamodel
44
+ loader + conformance + OMDB runtime persistence engine + the FR-004 render engine,
5-
plus the `metaobjects-maven-plugin` for build-time codegen (`meta:gen` / `meta:editor`).
5+
plus the `metaobjects-maven-plugin` for build-time codegen (`mvn metaobjects:generate` / `metaobjects:editor`).
66

77
Schema migrations are owned by the TypeScript toolchain (`@metaobjectsdev/cli migrate`);
88
the Java diff-and-converge migration engine and its `meta:migrate` / live-DB-drift
@@ -12,8 +12,15 @@ Prompt / template drift is still checked via the `metaobjects-render` `Verify` A
1212

1313
## Install
1414

15+
Set `${metaobjects.version}` to the current Maven Central release (`7.11.3`) — both
16+
the dependency and plugin blocks below resolve it from one `<properties>` entry:
17+
1518
```xml
1619
<!-- pom.xml -->
20+
<properties>
21+
<metaobjects.version>7.11.3</metaobjects.version>
22+
</properties>
23+
1724
<dependencies>
1825
<dependency>
1926
<groupId>com.metaobjects</groupId>
@@ -55,7 +62,19 @@ For Spring integration: add `metaobjects-core-spring`.
5562
</loader>
5663
<generators>
5764
<generator>
58-
<classname>com.metaobjects.generator.java.JavaPojoGenerator</classname>
65+
<classname>com.metaobjects.generator.spring.SpringDtoGenerator</classname>
66+
<args>
67+
<outputDir>${project.build.directory}/generated-sources/java</outputDir>
68+
</args>
69+
</generator>
70+
<generator>
71+
<classname>com.metaobjects.generator.spring.SpringControllerGenerator</classname>
72+
<args>
73+
<outputDir>${project.build.directory}/generated-sources/java</outputDir>
74+
</args>
75+
</generator>
76+
<generator>
77+
<classname>com.metaobjects.generator.spring.SpringRepositoryGenerator</classname>
5978
<args>
6079
<outputDir>${project.build.directory}/generated-sources/java</outputDir>
6180
</args>
@@ -147,37 +166,61 @@ auto-create path was removed per ADR-0015.
147166
OMDB reads the same metadata at runtime and drives CRUD; no per-entity ORM
148167
boilerplate.
149168

169+
The Java port generates **no typed entity POJO** — the only entity-shaped Java
170+
output is the immutable `<Entity>Dto` record (from `codegen-spring`). OMDB drives
171+
CRUD against the loaded metadata plus generic `ValueObject` instances, and its API
172+
is connection-first (you pass an `ObjectConnection` to each call):
173+
150174
```java
151175
import com.metaobjects.loader.MetaDataLoader;
152-
import com.metaobjects.omdb.ObjectManagerDb;
153-
import com.metaobjects.object.ValueObject;
154-
import acme.blog.Author;
155-
176+
import com.metaobjects.manager.ObjectConnection;
177+
import com.metaobjects.manager.QueryOptions;
178+
import com.metaobjects.manager.db.ObjectManagerDB;
179+
import com.metaobjects.manager.exp.Expression;
180+
import com.metaobjects.object.MetaObject;
181+
import com.metaobjects.object.value.ValueObject;
182+
183+
import javax.sql.DataSource;
156184
import java.nio.file.Path;
157-
import java.util.List;
185+
import java.util.Collection;
158186

159187
public class App {
160188
public static void main(String[] args) throws Exception {
161189
MetaDataLoader loader = MetaDataLoader.fromDirectory(
162190
"app", Path.of("src/main/metaobjects"));
163191

164-
ObjectManagerDb om = ObjectManagerDb.builder()
165-
.loader(loader)
166-
.dataSource(/* javax.sql.DataSource */)
167-
.build();
168-
169-
// CRUD
170-
Author author = new Author();
171-
author.setName("Ada");
172-
om.persist(author);
173-
174-
List<Author> all = om.getObjectsBy(Author.class, new ValueObject());
175-
Author fetched = om.getObjectById(Author.class, author.getId());
192+
DataSource ds = /* your javax.sql.DataSource */;
193+
194+
ObjectManagerDB om = new ObjectManagerDB();
195+
om.setDataSource(ds);
196+
om.init();
197+
198+
MetaObject author = loader.getMetaObjectByName("acme::blog::Author");
199+
200+
ObjectConnection oc = om.getConnection();
201+
try {
202+
// CREATE — a generic ValueObject typed by the Author MetaObject
203+
ValueObject row = (ValueObject) author.newInstance();
204+
row.setString("name", "Ada");
205+
om.createObject(oc, row);
206+
oc.commit();
207+
208+
// QUERY — all rows, or filtered via an Expression
209+
Collection<?> all = om.getObjects(oc, author, new QueryOptions());
210+
ValueObject match = (ValueObject) om.getObjects(
211+
oc, author, new QueryOptions(new Expression("name", "Ada")))
212+
.iterator().next();
213+
214+
// LOAD by primary key — re-reads the row into the object
215+
om.loadObject(oc, match);
216+
} finally {
217+
om.releaseConnection(oc);
218+
}
176219
}
177220
}
178221
```
179222

180-
Spring wiring lives in `metaobjects-core-spring`; declare an `ObjectManagerDb`
223+
Spring wiring lives in `metaobjects-core-spring`; declare an `ObjectManagerDB`
181224
bean with the Spring `DataSource` and let Spring inject it into your services.
182225

183226
## FR-004 — render engine
@@ -190,20 +233,23 @@ import java.util.Map;
190233

191234
Provider provider = new FilesystemProvider(Path.of("./prompts"));
192235

193-
String out = Renderer.render(RenderRequest.builder()
194-
.ref("lobby/welcome")
195-
.payload(Map.of(
196-
"displayName", "Ada",
197-
"postCount", 12L,
198-
"posts", List.of(Map.of("title", "Hello"))))
199-
.provider(provider)
200-
.format("xml")
201-
.build());
236+
Map<String, Object> payload = Map.of(
237+
"displayName", "Ada",
238+
"postCount", 12L,
239+
"posts", List.of(Map.of("title", "Hello")));
240+
241+
// RenderRequest is a record (template, ref, payload, provider, format, verify, maxChars);
242+
// pass a null template for a provider-resolved ref, and null verify/maxChars.
243+
// render() is an instance method.
244+
String out = new Renderer().render(
245+
new RenderRequest(null, "lobby/welcome", payload, provider, "xml", null, null));
202246
```
203247

204-
`Verify.verify(loader, provider, options)` drift-checks every `template.*` node
205-
against its `@payloadRef`. Wire it into a Maven test (e.g. a JUnit assertion in
206-
the `test` phase).
248+
`Verify.check(templateText, fields, options)` returns a `List<VerifyError>` (empty
249+
= no drift) — it cross-checks a template's variables against its declared payload
250+
field tree (`List<PayloadField>`), flagging any variable absent from the payload
251+
(`ERR_VAR_NOT_ON_PAYLOAD`), unresolved partials, and unused required slots. Wire it
252+
into a Maven test (e.g. a JUnit assertion in the `test` phase).
207253

208254
## Generators
209255

@@ -248,7 +294,7 @@ configuration model that has not yet been specced.
248294
| Payload-VO codegen | Yes — `SpringPayloadGenerator` (in `metaobjects-codegen-spring`) emits a Java 21 `record` per template, mirrors the Kotlin shape |
249295
| Output parser codegen (FR-006) | Yes — `SpringOutputParserGenerator` (in `metaobjects-codegen-spring`) — see usage below |
250296
| Migrations | TS-only (`@metaobjectsdev/cli migrate`) — the Java migration engine and the OMDB runtime auto-create path were both removed (ADR-0015); apply the TS-produced DDL to the database |
251-
| Drift verify | `Renderer.verify` / `Verify.verify` (prompts). Live-DB schema-drift verification is part of the TS migration toolchain |
297+
| Drift verify | `Verify.check` / `Verify.checkOutputPrompt` (prompts). Live-DB schema-drift verification is part of the TS migration toolchain |
252298
| Runtime metadata | Full — OMDB ObjectManager |
253299
| REST controller codegen | Spring Web MVC — `metaobjects-codegen-spring` (FR-008 §2.1) |
254300

@@ -287,9 +333,10 @@ try {
287333
}
288334
```
289335

290-
`Verify.verify(loader, provider, options)` walks `template.output` nodes the
291-
same way it walks `template.prompt`, catching payload-VO ↔ parser drift at
292-
build time. Cross-port design is at
336+
The same `Verify` API guards the output side: `Verify.checkOutputPrompt(fragment,
337+
requiredFieldNames)` checks the output-format prompt fragment names every required
338+
field, and `Verify.check(...)` (with output-tag slots supplied via its
339+
`VerifyOptions`) catches payload-VO ↔ parser drift at build time. Cross-port design is at
293340
[ADR-0010](../../spec/decisions/ADR-0010-template-output-parser-codegen.md);
294341
the feature reference is at
295342
[`features/templates-and-payloads.md`](../features/templates-and-payloads.md#output-parsing-fr-006).

docs/ports/kotlin.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ wiring) via KotlinPoet.
99

1010
Two modules:
1111

12-
- **`metaobjects-codegen-kotlin`**7 KotlinPoet-based generators.
12+
- **`metaobjects-codegen-kotlin`**14 KotlinPoet-based generators.
1313
- **`metaobjects-metadata-ktx`** — thin Kotlin facade over the Java loader + render
1414
engine for idiomatic Kotlin runtime use.
1515

@@ -69,15 +69,20 @@ Two modules:
6969

7070
## Configure
7171

72-
The 9 generators in `codegen-kotlin`:
72+
The 14 generators registered in `codegen-kotlin` (`GeneratorRegistry.kt`):
7373

7474
| Generator | Output | Per |
7575
|---|---|---|
76-
| `KotlinEntityGenerator` | `<Entity>.kt` — Kotlin `data class` (Jackson-compatible; no `@Serializable`) | every `object.entity` + `object.value` |
76+
| `KotlinEntityGenerator` | `<Entity>.kt` — Kotlin `data class` (Jackson-compatible; no `@Serializable`) | every `object.entity`, `object.value`, and `object.projection` |
7777
| `KotlinExposedTableGenerator` | `<Entity>Table.kt` — Exposed `Table` object with PK + FK + `@storage` columns | entities with `source.rdb` |
7878
| `KotlinRelationsGenerator` | `<Entity>Relations.kt` — extension fns for `cardinality=many` query helpers | entities with to-many relationships |
79+
| `KotlinRepositoryGenerator` | `<Entity>RepositoryBase.kt` — persistence repository base (row-mapper + CRUD + patch) | writable entities (`source.rdb @kind="table"`) |
80+
| `KotlinFilterAllowlistGenerator` | `<Entity>FilterAllowlist.kt` — FR-009 filter allowlist (filterable field names + allowed ops per field) | writable entities (`source.rdb @kind="table"`) |
7981
| `KotlinPayloadGenerator` | `<Template>Payload.kt``@Serializable` payload from `@payloadRef` view-object | every `template.prompt` / `template.output` |
8082
| `KotlinOutputParserGenerator` | `<Template>Parser.kt``object` with `parseXxx` (throws `SerializationException`) + `safeParseXxx` (returns `Result<TPayload>`) | every `template.output` (FR-006) |
83+
| `KotlinOutputPromptGenerator` | `<Template>Prompt.kt` — output-format prompt fragment (FR-010) | every `template.output` |
84+
| `KotlinRenderHelperGenerator` | `<Template>RenderHelper.kt` — typed `render()` wrappers (document/email, keyed off `@kind`) | every `template.output` |
85+
| `KotlinExtractorGenerator` | `<Template>Extractor.kt` — strict typed `extract<Name>` payload helper (FR-010) | every nested-capable `template.output` |
8186
| `KotlinValidatorGenerator` | `MetadataStartupValidator.kt` + `ExposedTableValidator.kt` | once per project |
8287
| `KotlinSpringConfigGenerator` | `MetadataExposedConfig.kt``@Configuration` wiring `Database.connect()` + auto-validator | once per project |
8388
| `KotlinStoredProcGenerator` | Stored-procedure call wrappers | entities with `source.rdb @kind="storedProc"` |
@@ -158,11 +163,17 @@ For the `Author` example (see [entities.md](../features/entities.md)), the codeg
158163
emits:
159164

160165
```kotlin
161-
// generated/acme/blog/Author.kt
162-
data class Author(
163-
val id: Long,
164-
val name: String,
165-
val bio: String? = null,
166+
// generated/acme/blog/Author.kt (jakarta.validation imports elided)
167+
/**
168+
* GENERATED — do not hand-edit. Regenerated from metadata.
169+
*/
170+
public data class Author(
171+
public val id: Long? = null, // field.long PK → nullable, auto-assigned on insert
172+
@field:NotNull
173+
@field:Size(min = 1, max = 200)
174+
public val name: String, // @required + @maxLength: 200
175+
@field:Size(max = 2000)
176+
public val bio: String? = null, // optional + @maxLength: 2000
166177
)
167178

168179
// generated/acme/blog/AuthorTable.kt
@@ -327,7 +338,8 @@ the contract is universal.
327338

328339
## Test count
329340

330-
122 tests in `codegen-kotlin` (`mvn -pl codegen-kotlin test`). Snapshot tests gate
341+
Several hundred tests in `codegen-kotlin` (`mvn -pl codegen-kotlin test`; ~290
342+
`@Test` methods across ~50 test files). Snapshot tests gate
331343
within-Java output stability; `kotlin-compile-testing` gates generated-code
332344
validity; an end-to-end test exercises the full loop including the Java
333345
`Renderer`. Persistence-conformance + the cross-port API contract run in

0 commit comments

Comments
 (0)