cody-information
v1.0.0Cody Information Skill - Specification for creating Information element details in prooph board. Defines schema, UI schema, query schema, query resolver, configuration, initialize, and projection for read models.
cody-information
Define Information (read model) element details for the Cody Engine low-code platform.
Overview
This skill teaches AI agents how to create detailed specifications for Information elements in Cody Engine, the low-code platform behind prooph board.
Important: This is a Cody Engine-specific skill. It is only useful if you work with the Cody Engine to generate applications from Event Models. If you use prooph board purely for Event Modeling without Cody Engine, this skill does not apply to you.
An Information element in Cody Engine represents data that can be read from the system — including queries, read models, and UI data bindings. The skill covers schemas, UI presentation, query resolvers, projections, and configuration metadata.
What This Skill Covers
- Schema — Data structure for single entities and lists using
cody-schemablocks - UI Schema — Table and form presentation using
cody-ui-schemablocks - Query Schema & Resolver — Input parameters and data fetching rules using
cody-query-schemaandcody-resolveblocks - Configuration Metadata — Collection and entity metadata using
cody-metadatablocks - Initialize Rules — Default value initialization for new items
- Projections — Event-sourced read models using
cody-projectionblocks
Why This Skill
- Complete read model specs — Covers everything from data structure to UI presentation to query logic
- Projection patterns — Teaches how to build read models from events using the Rule Engine
- UI integration — Ensures information elements include proper table and form configurations
When to Use
| ✅ Use This Skill | ❌ Skip It |
|---|---|
| Modeling information elements for Cody Engine applications | Using prooph board for Event Modeling only (no Cody Engine) |
| Defining queryable data and read models | General code generation for other frameworks |
| Without knowledge of the Cody Engine Rule Engine |
Usage
Once installed, your AI agent will know how to create structured information specifications with schema, UI configuration, query resolvers, and projections.
Examples
Prerequisites
- Familiarity with the Cody Engine low-code platform
- Understanding of read models and CQRS patterns
- Knowledge of the Rule Engine and JEXL expressions
Cody Information Skill - Information Element Details Specification
This document describes the structure and patterns for creating Information element details in the Cody/prooph board system.
Overview
Information elements represent data that can be read from the system. They define queryable data structures, their presentation, and how they're retrieved from the database.
Information details contain structured markdown with code blocks that define:
- Schema - Data structure and validation
- UI Schema - Presentation configuration (tables, forms, etc.)
- Query Schema - Input parameters for queries
- Query Resolver - How to fetch the data
- Configuration - Collection and entity metadata
- Initialize - Default value initialization (optional)
- Projection - Event-sourced projections (for read models)
Related Documentation
- Rule Engine — Details on the rule engine used for query resolvers, projections, and initialization rules
- JEXL Expressions — Documentation for JEXL expressions used in
cody-resolve,cody-projection,cody-initialize,cody-schema, andcody-ui-schemacode blocks
Structure Template
For Entity (Single Item)
```markdown
## Schema
```cody-schema
{
"id": "string|format:uuid|title:Id",
"name": "string",
"field?": "type"
}
```
## UI Schema
```cody-ui-schema
{
"id": {
"ui:widget": "hidden"
}
}
```
## Query Schema
`````cody-query-schema
{
"id": "string|format:uuid"
}
```
## Query Resolver
```cody-resolve
{
"where": {
"rule": "always",
"then": {
"filter": {
"eq": {
"prop": "id",
"value": "$> query.id"
}
}
}
}
}
```
## Configuration
```cody-metadata
{
"hasIdentifier": true,
"identifier": "id",
"isList": false,
"isQueryable": true,
"query": "App.GetItem",
"collection": "items_collection",
"ns": "/App",
"entity": true
}
```
## Initialize
```cody-initialize
[]
```
For List
```markdown
## Schema
```cody-schema
{
"$items": "/App/ItemType"
}
```
## UI Schema
```cody-ui-schema
{
"ui:table": {
"columns": [
"field1",
"field2"
]
}
}
```
## Query Schema
```cody-query-schema
{}
```
## Query Resolver
```cody-resolve
{
"where": {
"rule": "always",
"then": {
"filter": {
"any": true
}
}
}
}
```
## Configuration
```cody-metadata
{
"ns": "/App",
"collection": "items_collection",
"entity": false,
"identifier": "id"
}
```
Detailed Specifications
1. Schema Definition
Required for all information elements
Single Entity Schema
{
"id": "string|format:uuid|title:Id",
"requiredField": "type|constraints",
"optionalField?": "type",
"nestedObject": {
"field": "type"
},
"$title": "Display Title"
}
List Schema
{
"$items": "/App/ItemType",
"$title": "Items"
}
Schema with Nested Objects
{
"contractId": "string|format:uuid",
"contractPartner": {
"lastName": "string|minLength:1",
"firstName": "string|minLength:1",
"address": {
"street": "string",
"streetNumber": "string",
"zipCode": "string",
"city": "string"
}
},
"child": {
"lastName": "string",
"firstName": "string",
"birthday?": "string|format:date"
}
}
Schema with Enum and Arrays
{
"status": "enum:new,reached,not_reached,contract_signed",
"channel": "enum:website,phone,email",
"children?": {
"$items": {
"firstName": "string",
"birthday?": "string|format:date"
}
},
"weekDays": {
"mon": "boolean|default:true",
"tue": "boolean|default:true",
"$title": "Week Days"
}
}
Schema with Reference Types
{
"locationId": "/App/Location:locationId",
"leadId?": "/App/Lead:leadId",
"bankAccount": "/App/BankAccount:bankAccountId"
}
2. UI Schema
Optional - defines how data is presented
Hidden Fields
{
"id": {
"ui:widget": "hidden"
},
"internalField": {
"ui:widget": "hidden"
}
}
DataSelect Widget
{
"locationId": {
"ui:widget": "DataSelect",
"ui:options": {
"data": "/App/Locations",
"value": "$> data.locationId",
"text": "$> data.name"
},
"ui:title": "Location"
}
}
Textarea Widget
{
"comment": {
"ui:title": "Additional comment",
"ui:widget": "textarea",
"ui:readonly": true,
"ui:options": {
"rows": 5
}
}
}
Table Configuration (List Views)
{
"ui:title": "",
"ui:table": {
"columns": [
"firstName",
"lastName",
"email",
{
"field": "status",
"headerName": "Status"
},
{
"field": "dateField",
"headerName": "Date",
"value": "$> row.dateField|date()"
},
{
"field": "nestedObject",
"value": "$> '{{row.nestedObject.firstName}} {{row.nestedObject.lastName}}'"
},
{
"field": "conditional",
"value": "$> row.viewingAppointment? row.viewingAppointment|date() : '-'"
},
{
"field": "actions",
"type": "actions",
"actions": [
{
"type": "link",
"pageLink": {
"page": "App.ItemDetails",
"mapping": {
"itemId": "$> row.itemId"
}
},
"button": {
"label": "View Details",
"icon": "open-in-new",
"color": "default"
}
}
]
}
]
}
}
UI Options with Actions
{
"ui:options": {
"actions": []
}
}
List Item with Custom Styling
{
"ui:list": {
"items": {
"ui:title:expr": "$> data.name",
"ui:options": {
"grid": {
"props": {
"sx": {
"border": 1,
"borderRadius": 1,
"borderColor": "grey.300",
"padding:expr": "$> theme.spacing|call(2)"
}
}
},
"actions": [
{
"type": "link",
"pageLink": {
"page": "App.ContactFormStep1",
"mapping": {
"locationId": "$> data.locationId"
}
},
"position": "bottom-left",
"button": {
"label": "Contact"
}
}
]
},
"hiddenField": {
"ui:widget": "hidden"
}
}
}
}
3. Query Schema
Required for queryable information - defines input parameters
No Parameters
{}
With Parameters
{
"itemId": "string|format:uuid",
"$title": "Get Item"
}
Multiple Parameters
{
"locationId": "string|format:uuid",
"status": "string",
"$title": "Get Location Leads"
}
4. Query Resolver
Required for queryable information - defines how to fetch data
Query resolvers use the Rule Engine to define data retrieval rules including
wherefilters,orderBysorting, andfindById/findlookups.
Empty Resolver (returns all)
{}
Simple Filter by ID
{
"where": {
"rule": "always",
"then": {
"filter": {
"eq": {
"prop": "itemId",
"value": "$> query.itemId"
}
}
}
}
}
Filter with Role-Based Access
{
"rules": [
{
"rule": "condition",
"if_not": "$> meta.user|role('KL')",
"then": {
"throw": {
"error": "$> 'Operation not allowed'"
}
}
}
],
"where": {
"rule": "always",
"then": {
"filter": {
"and": [
{
"eq": {
"prop": "locationId",
"value": "$> meta.user|attr('location', '__')"
}
},
{
"not": {
"anyOf": {
"prop": "status",
"valueList": "$> ['contract_signed', 'abandoned']"
}
}
}
]
}
}
},
"orderBy": {
"prop": "createdAt",
"sort": "desc"
}
}
Conditional Filter
{
"rules": [
{
"rule": "condition",
"if_not": "$> meta.user|role(['Admin', 'KL', 'Management'])",
"then": {
"throw": {
"error": "$> 'Operation not allowed'"
}
}
}
],
"where": {
"rule": "condition",
"if": "$> meta.user|role('KL')",
"then": {
"filter": {
"eq": {
"prop": "locationId",
"value": "$> meta.user|attr('location', '___')"
}
}
},
"else": {
"filter": {
"any": true
}
}
}
}
Complex Query with Multiple Steps
{
"rules": [
{
"rule": "always",
"then": {
"findById": {
"information": "/App/Lead",
"id": "$> query.leadId",
"variable": "lead"
}
}
},
{
"rule": "always",
"then": {
"find": {
"information": "/App/Parent",
"filter": {
"and": [
{
"eq": {
"prop": "lastName",
"value": "$> lead.lastName"
}
},
{
"eq": {
"prop": "firstName",
"value": "$> lead.firstName"
}
}
]
}
}
}
}
]
}
Filter with Date Conditions
{
"where": {
"rule": "always",
"then": {
"filter": {
"and": [
{
"lte": {
"prop": "attempts",
"value": "$> 3"
}
},
{
"lte": {
"prop": "deliverAt",
"value": "$> now()|isoDateTime()"
}
}
]
}
}
}
}
5. Configuration Metadata
Required for all information elements
Entity Configuration
{
"hasIdentifier": true,
"identifier": "itemId",
"isList": false,
"isQueryable": true,
"query": "App.GetItem",
"collection": "items_collection",
"ns": "/App",
"entity": true
}
List Configuration
{
"hasIdentifier": true,
"isList": true,
"isQueryable": true,
"itemIdentifier": "itemId",
"itemType": "App.Item",
"query": "App.GetItems",
"collection": "items_collection",
"ns": "/App",
"entity": false
}
Simple Collection (No Query)
{
"ns": "/App",
"collection": "items_collection",
"entity": false,
"identifier": "itemId"
}
Static View (No Collection)
{
"ns": "/App",
"collection": false
}
Queryable List (Not Tied to Collection)
{
"ns": "/App",
"collection": false,
"entity": false,
"identifier": "parentId"
}
6. Initialize
Optional - default values for new items
Initialize rules use the Rule Engine to set default values when creating new items.
[]
With Default Values
[
{
"rule": "condition",
"if_not": "$> data.groups",
"then": {
"assign": {
"variable": "data",
"value": "$> data|set('groups', [])"
}
}
}
]
7. Projection (Event-Sourced Read Models)
Optional - for building read models from events
Projections use the Rule Engine to define event handlers with
when/given/thenclauses. Each case handles one event type, withupsertactions writing to the read model.
Projection with Multiple Event Handlers
{
"name": "MailOutboxProjection",
"live": true,
"cases": [
{
"when": "Lead Submitted",
"given": [
{
"rule": "always",
"then": {
"assign": {
"variable": "leadMailId",
"value": "$> uuid()"
}
}
},
{
"rule": "always",
"then": {
"findById": {
"information": "/App/Location",
"id": "$> event.locationId",
"variable": "location"
}
}
}
],
"then": {
"execute": {
"rules": [
{
"rule": "always",
"then": {
"upsert": {
"information": "/App/MailOutbox",
"id": "$> leadMailId",
"set": {
"mailId": "$> leadMailId",
"recipients": [
{
"to": "$> event.email"
}
],
"template": "$> 'lead-thank-you'",
"variables": {
"firstName": "$> event.firstName",
"lastName": "$> event.lastName",
"locationName": "$> location.name"
},
"deliverAt": "$> eventCreatedAt",
"attempts": "$> 0"
}
}
}
}
]
}
}
},
{
"when": "Lead Not Reached By Phone",
"given": [
{
"rule": "always",
"then": {
"assign": {
"variable": "mailId",
"value": "$> uuid()"
}
}
},
{
"rule": "always",
"then": {
"findById": {
"information": "/App/Lead",
"id": "$> event.leadId",
"variable": "lead"
}
}
}
],
"then": {
"upsert": {
"id": "$> mailId",
"set": {
"mailId": "$> mailId",
"recipients": [
{
"to": "$> lead.email"
}
],
"template": "$> 'lead-not-reached'",
"variables": {
"firstName": "$> lead.firstName",
"lastName": "$> lead.lastName"
},
"deliverAt": "$> eventCreatedAt",
"attempts": "$> 0"
}
}
}
}
]
}
Complete Examples
Example 1: Single Entity (Lead)
```markdown
## Schema
```cody-schema
{
"leadId": "string|format:uuid",
"firstName": "string",
"lastName": "string",
"email": "string|format:email",
"phone?": "string",
"locationId": "/App/Location:locationId",
"status": "enum:new,reached,not_reached,contract_handed_over,contract_signed,abandoned",
"inquirySubmittedAt": "string|format:date-time",
"channel": "enum:website,phone,email",
"children?": {
"$items": {
"firstName": "string",
"birthday?": "string|format:date"
}
},
"viewingAppointment?": {
"at": "string|format:date-time",
"status": "enum:scheduled,took_place,no_show,cancelled|default:scheduled",
"notes?": "string"
}
}
```
## UI Schema
```cody-ui-schema
{
"leadId": {
"ui:widget": "hidden"
},
"locationId": {
"ui:widget": "DataSelect",
"ui:options": {
"data": "/App/Locations",
"value": "$> data.locationId",
"text": "$> data.name"
}
},
"comment": {
"ui:title": "Additional comment by lead",
"ui:widget": "textarea",
"ui:readonly": true,
"ui:options": {
"rows": 5
}
}
}
```
## Query Schema
```cody-query-schema
{
"leadId": "string|format:uuid"
}
```
## Query Resolver
```cody-resolve
{
"where": {
"rule": "always",
"then": {
"filter": {
"any": true
}
}
}
}
```
## Configuration
```cody-metadata
{
"ns": "/App",
"collection": "leads_collection",
"identifier": "leadId",
"entity": true
}
```
Example 2: List with Table View (My Location Leads)
```markdown
## Schema
```cody-schema
{
"$items": "/App/Lead"
}
```
## UI Schema
```cody-ui-schema
{
"ui:title": false,
"ui:table": {
"columns": [
"firstName",
"lastName",
"email",
"phone",
{
"field": "status",
"headerName": "Status"
},
{
"field": "channel"
},
{
"field": "inquirySubmittedAt",
"headerName": "Inquiry From",
"value": "$> row.inquirySubmittedAt|date()"
},
{
"field": "viewingAppointment",
"headerName": "Viewing",
"value": "$> row.viewingAppointment? row.viewingAppointment|date() : '-'"
},
{
"field": "actions",
"type": "actions",
"actions": [
{
"type": "link",
"pageLink": {
"page": "App.LeadDetails",
"mapping": {
"leadId": "$> row.leadId"
}
},
"button": {
"label": "View Details",
"icon": "open-in-new",
"color": "default"
}
}
]
}
]
}
}
```
## Query Schema
```cody-query-schema
{}
```
## Query Resolver
```cody-resolve
{
"rules": [
{
"rule": "condition",
"if_not": "$> meta.user|role('KL')",
"then": {
"throw": {
"error": "$> 'Operation not allowed'"
}
}
}
],
"where": {
"rule": "always",
"then": {
"filter": {
"and": [
{
"eq": {
"prop": "locationId",
"value": "$> meta.user|attr('location', '__')"
}
},
{
"not": {
"anyOf": {
"prop": "status",
"valueList": "$> ['contract_signed', 'abandoned']"
}
}
}
]
}
}
},
"orderBy": {
"prop": "inquirySubmittedAt",
"sort": "desc"
}
}
```
## Configuration
```cody-metadata
{
"ns": "/App",
"collection": "leads_collection",
"entity": false,
"identifier": "leadId"
}
```
Example 3: Entity with Nested Objects (Contract)
```markdown
## Schema
```cody-schema
{
"contractId": "string|format:uuid|title:Contract Id",
"locationId": "/App/Location:locationId",
"leadId?": "/App/Lead:leadId",
"contractPartner": {
"lastName": "string|minLength:1",
"firstName": "string|minLength:1",
"address": {
"street": "string|minLength:1",
"streetNumber": "string|minLength:1",
"zipCode": "string|minLength:1",
"city": "string|minLength:1"
}
},
"child": {
"lastName": "string|minLength:1",
"firstName": "string|minLength:1",
"birthday?": "string|format:date"
},
"entryDate": "string|format:date|title:Entry Date",
"weekDays": {
"mon": "boolean|default:true|title:Mon",
"tue": "boolean|default:true|title:Tue",
"wed": "boolean|default:true|title:Wed",
"thu": "boolean|default:true|title:Thu",
"fri": "boolean|default:true|title:Fri",
"$title": "Week Days"
},
"careDetails": {
"allDay": "boolean|default:true|title:All Day",
"lunch": "boolean|default:true|title:Lunch"
},
"comments?": "string",
"monthlyRateBaby": "number|title:Monthly Rate Baby",
"monthlyRateKid": "number|title:Monthly Rate Kid",
"signed": "boolean|default:false",
"$title": "Contract"
}
```
## UI Schema
```cody-ui-schema
{
"contractId": {
"ui:widget": "hidden"
},
"locationId": {
"ui:widget": "DataSelect",
"ui:options": {
"data": "/App/Locations",
"label": "$> data.name",
"value": "$> data.locationId"
},
"ui:title": "Location Id"
},
"leadId": {
"ui:widget": "hidden"
}
}
```
## Query Schema
```cody-query-schema
{
"contractId": "string|format:uuid|title:Contract Id",
"$title": "Get Contract"
}
```
## Query Resolver
```cody-resolve
{}
```
## Configuration
```cody-metadata
{
"hasIdentifier": true,
"identifier": "contractId",
"isList": false,
"isQueryable": true,
"query": "App.GetContract",
"collection": "contracts_collection",
"ns": "/App",
"entity": true
}
```
## Initialize
```cody-initialize
[]
```
Example 4: Static View (Similar Parents Hint)
```markdown
## Queryable List
Not tied to a collection
## Schema
```cody-schema
{
"$items": "/App/Parent"
}
```
## UI Schema
```cody-ui-schema
{
"ui:table": {
"columns": [
"lastName",
"firstName"
]
}
}
```
## Query
```cody-query-schema
{
"leadId": "string|format:uuid"
}
```
## Query Resolver
```cody-resolve
{
"rules": [
{
"rule": "always",
"then": {
"findById": {
"information": "/App/Lead",
"id": "$> query.leadId",
"variable": "lead"
}
}
},
{
"rule": "always",
"then": {
"find": {
"information": "/App/Parent",
"filter": {
"and": [
{
"eq": {
"prop": "lastName",
"value": "$> lead.lastName"
}
},
{
"eq": {
"prop": "firstName",
"value": "$> lead.firstName"
}
}
]
}
}
}
}
]
}
```
## Configuration
```cody-metadata
{
"ns": "/App",
"collection": false,
"entity": false,
"identifier": "parentId"
}
```
Example 6: Projection Collection (Mail Outbox)
```markdown
## Todo List
Projection collection used as worker queue
## Todo Item Schema
```cody-schema
{
"$items": {
"mailId": "string|format:uuid",
"recipients": {
"$items": {
"to": "string|format:email",
"cc?": "boolean",
"bcc?": "boolean"
}
},
"template": "string",
"variables": {},
"deliverAt": "string|format:date-time",
"attempts": "integer|default:0",
"lastError?": "string"
}
}
```
## UI Schema
```cody-ui-schema
{
"ui:table": {
"columns": [
"template"
]
}
}
```
## Query
```cody-query-schema
{}
```
## Query Resolver
```cody-resolve
{
"where": {
"rule": "always",
"then": {
"filter": {
"and": [
{
"lte": {
"prop": "attempts",
"value": "$> 3"
}
},
{
"lte": {
"prop": "deliverAt",
"value": "$> now()|isoDateTime()"
}
}
]
}
}
}
}
```
## Projection
```cody-projection
{
"name": "MailOutboxProjection",
"live": true,
"cases": [
{
"when": "Lead Submitted",
"given": [
{
"rule": "always",
"then": {
"assign": {
"variable": "leadMailId",
"value": "$> uuid()"
}
}
},
{
"rule": "always",
"then": {
"findById": {
"information": "/App/Location",
"id": "$> event.locationId",
"variable": "location"
}
}
}
],
"then": {
"execute": {
"rules": [
{
"rule": "always",
"then": {
"upsert": {
"information": "/App/MailOutbox",
"id": "$> leadMailId",
"set": {
"mailId": "$> leadMailId",
"recipients": [
{
"to": "$> event.email"
}
],
"template": "$> 'lead-thank-you'",
"variables": {
"firstName": "$> event.firstName",
"lastName": "$> event.lastName",
"locationName": "$> location.name"
},
"deliverAt": "$> eventCreatedAt",
"attempts": "$> 0"
}
}
}
}
]
}
}
}
]
}
```
## Configuration
```cody-metadata
{
"ns": "/App",
"collection": "mail_outbox_collection",
"entity": false,
"identifier": "mailId"
}
```
Filter Operators
| Operator | Description | Example |
|---|---|---|
eq |
Equals | {"eq": {"prop": "status", "value": "new"}} |
ne |
Not equals | {"ne": {"prop": "status", "value": "deleted"}} |
lt |
Less than | {"lt": {"prop": "age", "value": 18}} |
lte |
Less than or equal | {"lte": {"prop": "attempts", "value": 3}} |
gt |
Greater than | {"gt": {"prop": "price", "value": 100}} |
gte |
Greater than or equal | {"gte": {"prop": "quantity", "value": 1}} |
in |
In list | {"in": {"prop": "status", "valueList": ["new", "pending"]}} |
not |
Negation | {"not": {"eq": {...}}} |
and |
Logical AND | {"and": [{"eq": {...}}, {"gt": {...}}]} |
or |
Logical OR | {"or": [{"eq": {...}}, {"eq": {...}}]} |
any |
Match any (no filter) | {"any": true} |
Expression Syntax
Available Variables
| Variable | Description |
|---|---|
$> query |
Query parameters |
$> row |
Current row in table columns |
$> data |
Current data item |
$> meta.user |
Current user |
$> event |
Event data (in projections) |
$> information |
Current state (in projections) |
$> eventCreatedAt |
Event timestamp |
Common Expressions
Date Formatting:
$> row.dateField|date()
$> row.inquirySubmittedAt|date()
Conditional Value:
$> row.viewingAppointment? row.viewingAppointment|date() : '-'
String Interpolation:
$> '{{row.child.firstName}} {{row.child.lastName}}'
$> '{{data.lastName}}, {{data.firstName}}'
User Properties:
$> meta.user|role('KL')
$> meta.user|attr('location', '__')
$> meta.user|role(['Admin', 'KL', 'Management'])
UUID Generation:
$> uuid()
Current Timestamp:
$> now()|isoDateTime()
Checklist for Creating New Information Elements
- Determine if it's a single entity or list
- Define schema with all fields and types
- Add optional fields with
?suffix - Configure UI schema for presentation
- Mark ID fields as hidden widgets
- Configure DataSelect for reference fields
- Define query schema (if queryable)
- Create query resolver with appropriate filters
- Add role-based access control if needed
- Set configuration metadata
- Specify collection and namespace
- Add initialize block if defaults needed
- Create projection if event-sourced read model
Information Types Summary
| Type | Description | Example |
|---|---|---|
| Entity | Single item from collection | Lead, Contract, Location |
| List | Multiple items with table view | Contracts, My Location Leads |
| Queryable List | Filtered list with custom query | Similar Parents for Lead |
| Static View | No data, just UI configuration | Similar Parents Hint |
| Projection | Event-sourced read model | Mail Outbox |