|
1 | 1 | # `metaobjects-codegen-kotlin` — known gaps |
2 | 2 |
|
3 | | -This document tracks deliberate Day-1 deferrals in the Kotlin codegen target. |
4 | | -Several gaps share the same rationale as the Java Spring codegen — those |
5 | | -sections point at |
6 | | -[`../codegen-spring/.../KNOWN_GAPS.md`](../codegen-spring/src/main/java/com/metaobjects/generator/spring/KNOWN_GAPS.md) |
7 | | -for the full reasoning rather than restating it. The gap itself lives in |
8 | | -the Kotlin codepath; only the rationale is shared. |
9 | | - |
10 | | -## Consumer dependency: `kotlinx-serialization-json` is required for FR-006 output |
11 | | - |
12 | | -**Status:** consumer-wired, not a code gap — documented here so adopters know what to add. |
13 | | - |
14 | | -`KotlinOutputParserGenerator` emits files that import |
15 | | -`kotlinx.serialization.json.Json` and call `Json.decodeFromString<T>(text)`. |
16 | | -The `kotlinx-serialization-core` artifact (already pulled in transitively |
17 | | -by anything using `@Serializable`) does NOT include the JSON format. |
18 | | -Consumers using FR-006 output-parser generation must add to their build: |
19 | | - |
20 | | -```kotlin |
21 | | -plugins { kotlin("plugin.serialization") version "1.9.x" } |
22 | | -dependencies { |
23 | | - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.x") |
24 | | -} |
25 | | -``` |
26 | | - |
27 | | -`KotlinPayloadGenerator` (which emits the `@Serializable` data class the |
28 | | -parser returns) needs the same plugin + the `core` artifact. The codegen |
29 | | -module itself depends only on `kotlinx-serialization-core-jvm` for its own |
30 | | -tests; **consumer-side wiring is the consumer's responsibility**, in line |
31 | | -with the cross-port pattern (TS consumers add `zod`; C# uses BCL |
32 | | -`System.Text.Json` — no add needed there; Python consumers add `pydantic`). |
33 | | - |
34 | | -## Consumer dependency: Jackson is required for typed jsonb columns |
35 | | - |
36 | | -**Status:** consumer-wired, not a code gap — documented here so adopters know what to add. |
37 | | - |
38 | | -`KotlinExposedTableGenerator` emits one shared `MetaJsonbMapper.kt` support file per |
39 | | -package that declares at least one typed `field.object @storage:jsonb` (single or |
40 | | -`@isArray` array-of-VO) or `field.map` column. That file holds an |
41 | | -`internal metaJsonbMapper` — a `com.fasterxml.jackson.databind.ObjectMapper` built from |
42 | | -`JsonMapper.builder().addModule(kotlinModule()).addModule(JavaTimeModule()) |
43 | | -.disable(WRITE_DATES_AS_TIMESTAMPS)` — that the generated `jsonb()` column codecs |
44 | | -read/write through (a `TypeReference<List<VO>>` captures the array-of-VO generic). |
45 | | - |
46 | | -Jackson — not kotlinx — is the codec precisely so the generated entity/value/projection |
47 | | -data classes carry NO `@Serializable` and need NO per-type serializer plumbing: a kotlinx |
48 | | -serializer (`VO.serializer()`) would require the `kotlin("plugin.serialization")` |
49 | | -compiler plugin, and the moment that plugin is on, every VO carrying a |
50 | | -`java.util.UUID` / `java.time.*` / `java.math.BigDecimal` / `java.net.*` field fails to |
51 | | -compile (kotlinx has no serializer for those `java.*` types). Jackson round-trips them |
52 | | -via its kotlin + jsr310 modules with zero per-type wiring. Consumers generating any typed |
53 | | -jsonb/map column must add to their build: |
54 | | - |
55 | | -```kotlin |
56 | | -dependencies { |
57 | | - implementation("com.fasterxml.jackson.core:jackson-databind:2.17.x") |
58 | | - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.x") |
59 | | - implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.x") |
60 | | -} |
61 | | -``` |
62 | | - |
63 | | -No `kotlin("plugin.serialization")` compiler plugin is needed for this path — the |
64 | | -generated codec is pure runtime Jackson. The separate `field.string @dbColumnType:jsonb` |
65 | | -"open-bag" column stays on the kotlinx lane above (it round-trips through the runtime |
66 | | -`Json.parseToJsonElement` → kotlinx `JsonElement` API, which also needs no compiler |
67 | | -plugin); only the typed object/map jsonb columns pull in Jackson. |
68 | | - |
69 | | -## Single-field, `Long`-typed primary keys only |
70 | | - |
71 | | -**Status:** assumption baked into Day 1 — same rationale as codegen-spring. |
72 | | - |
73 | | -`KotlinSpringControllerGenerator` assumes the entity's primary key is a single |
74 | | -field of type `Long` (the canonical `BaseEntity` convention across the |
75 | | -shared corpus). Composite primary keys would require a URL grammar for |
76 | | -composite ids that the cross-port contract has not yet specified. Entities |
77 | | -with non-`Long` single-field PKs (e.g. `UUID`) will still generate, but the |
78 | | -`@PathVariable id: Long` typing in the generated controller will need a |
79 | | -hand-edit until typed-PK threading lands. See the codegen-spring gap doc |
80 | | -for the cross-port "settle PK URL grammar first" reasoning that gates |
81 | | -fixing this in both ports together. |
82 | | - |
83 | | -## Single `<Entity>` data-class for request + response |
84 | | - |
85 | | -**Status:** intentional Day-1 simplification — parallel to codegen-spring's combined-DTO choice. |
86 | | - |
87 | | -The generated controller uses one `<Entity>` data class for both |
88 | | -request and response bodies across `POST` / `PATCH` / `PUT`. This differs |
89 | | -from the TS reference, which emits separate `<Entity>Insert` and |
90 | | -`<Entity>Update` shapes (Update is partial). The cross-port wire contract |
91 | | -holds — the body is the row in either direction, no envelope — so the |
92 | | -single-class shape interoperates correctly with the TS client. |
93 | | - |
94 | | -**Why deferred:** Kotlin's data-class `copy()` plus nullable properties |
95 | | -gives consumers a natural partial-update path on the client side, but |
96 | | -asymmetric request/response classes would still want field-by-field |
97 | | -nullable flagging (e.g. `name: String?` on the Update shape only). That |
98 | | -flagging needs metadata-level expression (an `@updateRequired` attr or |
99 | | -similar) that hasn't been settled cross-port; a follow-up FR can add |
100 | | -the typed split once the partial-update story converges. |
101 | | - |
102 | | -## `EnumField` on payload VOs emitted as `String` (RESOLVED) |
103 | | - |
104 | | -**Status:** RESOLVED — the payload-VO codepath now emits the typed enum class too, |
105 | | -so this former Day-1 gap is closed on every path. |
106 | | - |
107 | | -`KotlinPayloadGenerator.resolveFieldType`'s `field.enum` arm types the STRICT |
108 | | -payload property as the generated enum class |
109 | | -(`KotlinTypeMapper.enumTypeName(field, owner)`; single → `<Enum>`, array → |
110 | | -`List<<Enum>>`) and emits the enum file per run via `KotlinEnumEmitter`. The |
111 | | -lenient `<Name>Extracted` mirror deliberately stays `String` / `List<String?>` |
112 | | -(the extract mapper bridges `String` → enum via `valueOf`). Entity data classes |
113 | | -([`KotlinEntityGenerator.kt`](src/main/kotlin/com/metaobjects/generator/kotlin/KotlinEntityGenerator.kt)) |
114 | | -and Exposed columns |
115 | | -([`KotlinExposedTableGenerator.kt`](src/main/kotlin/com/metaobjects/generator/kotlin/KotlinExposedTableGenerator.kt)) |
116 | | -were typed all along. Kept as a resolved entry (rather than deleted) because |
117 | | -external notes referenced this section by title. |
118 | | - |
119 | | -## Composite-FK relationships not emitted |
120 | | - |
121 | | -**Status:** Day-1 limitation. [`KotlinExposedTableGenerator.kt:573`](src/main/kotlin/com/metaobjects/generator/kotlin/KotlinExposedTableGenerator.kt#L573) |
122 | | -warns and skips when `relationship.reference @fields:"a,b"` declares a |
123 | | -composite key. The single-field `@field:"x"` form is fully supported. |
124 | | - |
125 | | -**Why deferred:** Exposed's `reference()` DSL is single-column. Multi-column |
126 | | -FKs need `compositeForeignKey { ... }` blocks built from the `@fields` |
127 | | -list, plus the matching composite PK on the target table. Rare in the |
128 | | -corpus today; opens when a real consumer needs it. |
129 | | - |
130 | | -## `ObjectField` skipped in sort allowlist + `rowTo<Entity>` mapping |
131 | | - |
132 | | -**Status:** Day-1 limitation in [`KotlinSpringControllerGenerator.kt:228`](src/main/kotlin/com/metaobjects/generator/kotlin/KotlinSpringControllerGenerator.kt#L228). |
133 | | - |
134 | | -A `field.object` (jsonb or flattened storage) is not eligible for `?sort=` in |
135 | | -the generated controller and is skipped in the `rowTo<Entity>(...)` mapper |
136 | | -that materializes a `ResultRow` into the data class. Consumers can hand-write |
137 | | -both — and the metadata already gates `?sort=` against the allowlist, so |
138 | | -disallowed sorts return 400 honestly. |
139 | | - |
140 | | -**Why deferred:** sorting on a jsonb scalar is dialect-specific (Postgres |
141 | | -`->>` casting; SQLite no jsonb at all); the cross-port contract has not |
142 | | -specified the URL grammar for nested-jsonb sort. Materializing nested |
143 | | -jsonb on read needs an `@Contextual` kotlinx.serialization round-trip |
144 | | -plus a column-type wiring; both ship as a unit when a real consumer needs |
145 | | -the path. |
146 | | - |
147 | | -## `KotlinGenUtil.splitDottedRef` has zero in-repo callers |
148 | | - |
149 | | -**Status:** deliberately kept, not dead code — recorded here so it is not later |
150 | | -rediscovered as live. |
151 | | - |
152 | | -#270 (payload typing is declared-type-authoritative) deleted the payload |
153 | | -generator's `origin.*` dotted-ref navigation, which was the last in-repo caller |
154 | | -of [`KotlinGenUtil.splitDottedRef`](src/main/kotlin/com/metaobjects/generator/kotlin/KotlinGenUtil.kt). |
155 | | -The helper stays because `KotlinGenUtil` is deliberately `public` for adopters |
156 | | -subclassing a generator (see its class KDoc) — removing a public helper is an |
157 | | -API break out of proportion to the cleanup. Prune it in a future MAJOR. |
| 3 | +**The gap list for this module lives beside the generators**, at |
| 4 | +[`src/main/kotlin/com/metaobjects/generator/kotlin/KNOWN_GAPS.md`](src/main/kotlin/com/metaobjects/generator/kotlin/KNOWN_GAPS.md) |
| 5 | +— the same position `codegen-spring` keeps its own file in, and the one corrections |
| 6 | +actually land in. |
| 7 | + |
| 8 | +This file used to carry a second, differently-structured gap list of its own (#361). The two |
| 9 | +were not copies: each documented gaps the other did not, so the risk was never a stale |
| 10 | +duplicate but a reader finding one list at the module root and believing it was the list. |
| 11 | +Both sets now live in the canonical file; this page is a pointer so that reader lands there. |
0 commit comments