Skip to content

feat(intent): EXTENSION entities - extends: keyword + cross-model merge into the base table#6383

Merged
delchev merged 2 commits into
masterfrom
feat/extension-entity-merge
Jul 22, 2026
Merged

feat(intent): EXTENSION entities - extends: keyword + cross-model merge into the base table#6383
delchev merged 2 commits into
masterfrom
feat/extension-entity-merge

Conversation

@delchev

@delchev delchev commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Honor EXTENSION entities: cross-model field contribution, from intent to base table

The EDM editor has long offered an Extension Entity type with no generation support. This makes it real, and adds the intent-DSL authoring layer, so a module can contribute fields to an entity another module owns without forking it (a localisation adds a national id to a core Employee, an integration adds an external key, …).

Two layers

  1. Generation support (generate.mjs + generateUtils.js): before generating a project, scan sibling projects' .models for EXTENSION entities whose extensionReferencedModel names this project and fold them in (augmentWithExtensions); then merge each EXTENSION's properties into its base entity (mergeExtensionEntities), skipping the PK, audit columns, and any name the base already defines (core wins), and drop the EXTENSION. The added fields become real base-table columns — list filter/sort, form, REST and _LANG all native, no join. An extension whose base is absent owns no table.
  2. Intent DSL (extends:): extends: { model: <base-model>, entity: <base-entity> } (model in uses:; omit for same-model). The EDM generator marks the entity type=EXTENSION with the base reference and emits no perspective/nav/layout. Documented in the assistant guide. A contributed field may declare after: <baseField> / before: <baseField> to control where the merged column lands in the base's form/list (default: appended).

Backward-compatible: a model with no EXTENSION entities is untouched.

How it is used (in-workspace)

The merge runs at generation time by scanning the workspace — the base project and the extension project are both present, and the base is (re)generated after the extension exists so its table picks up the added fields. This is the normal dev/build flow: to work on an extension you have both models in your workspace. (How the generated output is subsequently packaged/deployed is outside the platform's concern.)

Verified end-to-end

An intent-authored extension module (extends: Employee adding an egn field) → the EXTENSION .model is emitted (type EXTENSION, base reference, Egn property) → regenerating the base folds it in: a real EMPLOYEE_BG_EGN column on the base Employee table, present in the entity class, the form and the manage-list, with no separate extension table. Unit tests: EDM emission (EdmIntentGeneratorTest), merge semantics (pk/audit skipped, core not overridden, orphan dropped).

Follow-up

_LANG merge test for a multilingual extension field; optional base-prefixed dataName for the contributed column (today it carries the contributing entity's prefix — good for provenance and collision-avoidance).

🤖 Generated with Claude Code

delchev and others added 2 commits July 22, 2026 16:15
…d fields into the base table at generation

An EXTENSION entity (already modellable in the EDM editor) had no generation
support: it fell through as a normal entity, getting its own table and no UI.
Now it is a true field contribution to another entity:

- generate.mjs (augmentWithExtensions): before generating a project, scan every
  sibling project's <name>.model for EXTENSION entities whose
  extensionReferencedModel names THIS project, and fold them into this model's
  entity list (deterministic order). This is the cross-model compose step - an
  extension module (e.g. a localisation adding EGN to Employee) contributes
  fields to an entity it does not own, resolved at generation time.
- generateUtils.generateFiles (mergeExtensionEntities): merge each EXTENSION's
  properties into its base entity (extensionReferencedEntity), skipping the pk,
  audit columns, and any name the base already defines (core wins); then drop
  the EXTENSION entities. The added fields become real columns on the base
  table, so list filter/sort/search, the form, REST and _LANG all treat them
  natively - no runtime join. An extension whose base is absent (its own
  project's generation) owns no table and is dropped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An entity can declare extends: { model: <base-model>, entity: <base-entity> }
(model in uses:; omit for same-model). The EDM generator marks it type=EXTENSION
with extensionReferencedModel/extensionReferencedEntity and emits no
perspective/nav/layout - its fields are folded into the base table by the
model-to-code merge (this branch's generateUtils.mergeExtensionEntities). The
authoring layer over the generation support: a localisation/integration module
contributes fields to an entity another module owns, from .intent. Test asserts
the EXTENSION type + base reference + field emission + no perspective.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@delchev delchev changed the title feat(intent): honor EXTENSION entities - merge cross-model contributed fields into the base table feat(intent): EXTENSION entities - extends: keyword + cross-model merge into the base table Jul 22, 2026
@delchev
delchev merged commit 1d1a09c into master Jul 22, 2026
10 checks passed
@delchev
delchev deleted the feat/extension-entity-merge branch July 22, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant