Descriptors and Primitives
Almost everything the application offers you — a menu entry, a toolbar button, a wizard, the write it performs when you press Finish — is a declaration rather than code. The generic runtime reads those declarations and does the work. That is why a capability pack can add a wizard to a running deployment without a new build.
Two layers, and it is worth keeping them apart:
A descriptor says where an action appears and what it opens. A transaction says what it writes, as a list of steps over a fixed catalogue of primitives.
The descriptor
A descriptor is a small JSON object. This one is the Show templates entry you use from a canvas node:
{
"id": "entityCanvas.showTemplates",
"labelKey": "capabilities.showTemplates.label",
"fallbackLabel": "Show templates",
"icon": "LayoutTemplate",
"hosts": ["contextMenu:entityCanvas"],
"handler": { "kind": "function", "ref": "entity.showTemplates" },
"order": 65,
"separatorBefore": true,
"testId": "canvas-node-ctx-show-templates"
}
hosts is where it appears. A descriptor can name several, which is how the same action
reaches you from the canvas, the tree and the right sidebar without being written three
times. The hosts in use today include toolbar:main, contextMenu:entityCanvas,
contextMenu:entityTree, contextMenu:groupTree, contextMenu:viewpointTree,
contextMenu:entitySidebar and drawer:browse.
handler is what pressing it does. Five kinds exist:
| Kind | Opens |
|---|---|
openForm | a dialog built from a field declaration |
openWizard | a multi-step form |
openDrawer | one of the browse drawers |
function | a named function in the client |
transaction | a write, with no dialog in between |
labelKey with fallbackLabel means a pack can ship its own translations and still
show something sensible when a locale is missing. order places the entry among its
neighbours; testId is what the end-to-end tests address it by.
Descriptors are validated against a schema when the registry boots. One that fails — a
misspelled field, a size the schema does not allow — is dropped silently, and the only
symptom is a menu entry or wizard that never appears. The client carries a test that parses
every shipped descriptor for exactly this reason.
Transactions and their primitives
A wizard that creates an entity, its identifier attribute, three custom-property values and a node on the canvas performs one transaction with five steps. Each step names an operation — and the operations are a fixed, closed catalogue: 56 of them today, declared as JSON beside the runtime rather than written per feature.
They are grouped by what they touch:
| Group | Operations over |
|---|---|
entity, entityGroup, node | entities, their groups, their placement on a canvas |
attribute | attributes, including create-and-link and reorder |
relation | relations between entities |
viewpoint, viewpointType | viewpoints and their types |
entityType, attributeType, relationType | the vocabulary |
entityCustomProperty, attributeCustomProperty, relationCustomProperty, modelCustomProperty | custom property definitions |
entityCPE, attributeCPE, relationCPE, modelCustomPropertyEntry | custom property values |
jsonSchema, sync | importing, and talking to the server |
Every operation declares three things: which store it writes, which mutation it is, and an input contract it validates against before writing.
| Mutation | Means |
|---|---|
create | add a record |
update | change one |
delete | remove one |
set_all | reconcile a whole map of related records in one step — used for custom property values, where "the ones you did not send" have to be deleted |
That contract is the reason a declaration cannot quietly write nonsense: a step whose input does not satisfy the operation's schema is refused, with the operation id in the message.
A pack can declare what to write, in what order, from which inputs — but not how to write. Everything goes through the same validated operations, the same store layer and the same journal, so an action added by a pack is undoable and auditable exactly like a built-in one.
Where to go next
- Custom wizards — a worked example: the descriptor, the fields, and the transaction behind a wizard that creates a business object.
- Packs — how a capability pack is installed, and why the application has to reload afterwards.