Summary
When two entities in different packages share one enum by pointing their fields at an abstract
field.enum via extends, the generated data classes emit the cross-package import correctly,
but the generated <Entity>Table and <Entity>RepositoryBase reference the enum by simple
name without importing it. The consuming module fails to compile.
extends itself works — the shared enum type is generated and the field resolves to it. Only the
import emission is inconsistent between generators.
This is the documented reuse mechanism ("Reuse a constraint set across entities with an abstract
field.enum + extends"), so in practice it only works when every consuming entity happens to sit
in the enum's own package.
Version: 7.8.0 (Maven), Kotlin + Exposed generators.
Reproduction
An abstract shared field in its own package:
# metadata/common/meta.record-status.yaml
metadata:
package: acme::common
children:
- field.enum:
name: RecordStatus
abstract: true
values: ["DRAFT", "ACTIVE", "CLOSED"]
Two entities in different packages extend it:
# metadata/orders/meta.order.yaml (package acme::orders)
- field.enum: { name: "status", required: true, extends: "acme::common::RecordStatus" }
# metadata/billing/meta.invoice.yaml (package acme::billing)
- field.enum: { name: "status", required: true, extends: "acme::common::RecordStatus" }
Then mvn generate-sources and compile the module.
Observed
acme/common/RecordStatus.kt is generated and the field type resolves to it. The import is emitted
by some generators and not others:
| generated file |
references the enum |
imports it |
orders/Order.kt (data class) |
yes |
✅ |
billing/Invoice.kt (data class) |
yes |
✅ |
| projection data class over either entity |
yes |
✅ |
orders/OrderTable.kt |
yes |
❌ |
billing/InvoiceTable.kt |
yes |
❌ |
orders/OrderRepositoryBase.kt |
yes |
❌ |
billing/InvoiceRepositoryBase.kt |
yes |
❌ |
The table generator does emit cross-package imports for referenced tables, so the import
machinery is present — only the enum is missed:
package acme.orders
import org.jetbrains.exposed.sql.Table
import acme.billing.InvoiceTable // cross-package TABLE import: present
// no import of acme.common.RecordStatus
object OrderTable : Table("orders") {
val status = enumerationByName("status", 64, RecordStatus::class) // unresolved
}
In our tree this produced 214 compile errors — Unresolved reference in each generated table,
cascading into argument-type-mismatch errors at every repository call site that passes the enum.
Expected
Any generated file that references a type resolved through extends should emit its import, matching
what the data-class generator already does.
Impact
There is no metadata-level workaround when the sharing entities live in different packages — the
abstract field can sit in at most one of them. Sharing one enum across packages is therefore not
expressible on 7.8.0, which leaves the vocabulary duplicated per entity and free to drift.
In our case the duplicated copies had already diverged (one had 10 members, the other 12) and the
divergence caused a silent runtime failure: a value legal in one table threw on write to the other,
inside a broad catch, so a whole record was dropped with only a warning logged. Consolidating the
declaration is what would have made that structurally impossible.
Separate observation — possibly its own issue
A child that declares its own values: alongside extends: has them silently ignored. No
warning, no error; the base's list simply wins. Measured on 7.8.0: a child declaring
extends: acme::common::RecordStatus plus values: ["ARCHIVED", "PURGED"] generated the base's
three members only.
If additive extension is intentionally unsupported (one shared enum meaning one shared type is a
reasonable design), then declaring both attributes should be a load error rather than a silent
drop — otherwise the authored metadata and the generated output disagree with nothing to signal it.
The wording of the authoring guidance ("reuse a constraint set across entities with an abstract
field.enum + extends") also reads as though additive extension might work, so a clarifying note
may be worthwhile either way.
Summary
When two entities in different packages share one enum by pointing their fields at an abstract
field.enumviaextends, the generated data classes emit the cross-package import correctly,but the generated
<Entity>Tableand<Entity>RepositoryBasereference the enum by simplename without importing it. The consuming module fails to compile.
extendsitself works — the shared enum type is generated and the field resolves to it. Only theimport emission is inconsistent between generators.
This is the documented reuse mechanism ("Reuse a constraint set across entities with an abstract
field.enum+extends"), so in practice it only works when every consuming entity happens to sitin the enum's own package.
Version: 7.8.0 (Maven), Kotlin + Exposed generators.
Reproduction
An abstract shared field in its own package:
Two entities in different packages extend it:
Then
mvn generate-sourcesand compile the module.Observed
acme/common/RecordStatus.ktis generated and the field type resolves to it. The import is emittedby some generators and not others:
orders/Order.kt(data class)billing/Invoice.kt(data class)orders/OrderTable.ktbilling/InvoiceTable.ktorders/OrderRepositoryBase.ktbilling/InvoiceRepositoryBase.ktThe table generator does emit cross-package imports for referenced tables, so the import
machinery is present — only the enum is missed:
In our tree this produced 214 compile errors —
Unresolved referencein each generated table,cascading into argument-type-mismatch errors at every repository call site that passes the enum.
Expected
Any generated file that references a type resolved through
extendsshould emit its import, matchingwhat the data-class generator already does.
Impact
There is no metadata-level workaround when the sharing entities live in different packages — the
abstract field can sit in at most one of them. Sharing one enum across packages is therefore not
expressible on 7.8.0, which leaves the vocabulary duplicated per entity and free to drift.
In our case the duplicated copies had already diverged (one had 10 members, the other 12) and the
divergence caused a silent runtime failure: a value legal in one table threw on write to the other,
inside a broad catch, so a whole record was dropped with only a warning logged. Consolidating the
declaration is what would have made that structurally impossible.
Separate observation — possibly its own issue
A child that declares its own
values:alongsideextends:has them silently ignored. Nowarning, no error; the base's list simply wins. Measured on 7.8.0: a child declaring
extends: acme::common::RecordStatusplusvalues: ["ARCHIVED", "PURGED"]generated the base'sthree members only.
If additive extension is intentionally unsupported (one shared enum meaning one shared type is a
reasonable design), then declaring both attributes should be a load error rather than a silent
drop — otherwise the authored metadata and the generated output disagree with nothing to signal it.
The wording of the authoring guidance ("reuse a constraint set across entities with an abstract
field.enum+extends") also reads as though additive extension might work, so a clarifying notemay be worthwhile either way.