Skip to main content

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.

From a descriptor to a writeA descriptor names a host, which is where it appears, and a handler, which is what it opens. A form or wizard collects input and dispatches a transaction. The transaction is a list of steps, each naming one operation from the catalogue. Each operation validates its input and writes to one store.Descriptorid, label, icon, orderhost — where it appearshandler — what it opensHosta context menu, thetoolbar, a drawer, aform sectionHandleropenForm, openWizard,openDrawer, function,transactionTransactionan ordered list of steps;each step names oneoperation and maps thecollected input into itOperationsthe primitives: create,update, delete, set_alleach validates its inputand writes one store

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:

KindOpens
openForma dialog built from a field declaration
openWizarda multi-step form
openDrawerone of the browse drawers
functiona named function in the client
transactiona 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.

An invalid descriptor is skipped, not reported

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:

GroupOperations over
entity, entityGroup, nodeentities, their groups, their placement on a canvas
attributeattributes, including create-and-link and reorder
relationrelations between entities
viewpoint, viewpointTypeviewpoints and their types
entityType, attributeType, relationTypethe vocabulary
entityCustomProperty, attributeCustomProperty, relationCustomProperty, modelCustomPropertycustom property definitions
entityCPE, attributeCPE, relationCPE, modelCustomPropertyEntrycustom property values
jsonSchema, syncimporting, 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.

MutationMeans
createadd a record
updatechange one
deleteremove one
set_allreconcile 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.

Why a closed catalogue

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.