mirror of https://github.com/abpframework/abp.git
committed by
GitHub
39 changed files with 2517 additions and 0 deletions
@ -0,0 +1,37 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "command-interceptor-descriptor.schema.json", |
|||
"title": "CommandInterceptorDescriptor", |
|||
"description": "Describes a JavaScript interceptor for an entity Create, Update, or Delete command.", |
|||
"markdownDescription": "AI guidance: use `type: \"Pre\"` to validate or block before persistence and `type: \"Post\"` for side effects after persistence. Scripts can inspect `context.commandArgs` and may use services exposed on `context` such as `db`, `currentUser`, `currentTenant`, `emailSender`, `config`, `http`, and logging helpers depending on host configuration. To block the command with a user-facing error, assign `globalError = \"message\"` and return. Keep scripts idempotent where possible.", |
|||
"type": "object", |
|||
"properties": { |
|||
"commandName": { |
|||
"type": "string", |
|||
"description": "Entity command to intercept: Create, Update, or Delete.", |
|||
"enum": ["Create", "Update", "Delete"] |
|||
}, |
|||
"type": { |
|||
"$ref": "interceptor-type.schema.json", |
|||
"description": "Whether the script runs before or after the command." |
|||
}, |
|||
"javascript": { |
|||
"type": "string", |
|||
"description": "JavaScript code to execute. For Pre interceptors, set globalError to block the command. Example: if (!context.commandArgs.data['Name']) { globalError = 'Name is required.'; }" |
|||
} |
|||
}, |
|||
"required": ["commandName", "type", "javascript"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"commandName": "Create", |
|||
"type": "Pre", |
|||
"javascript": "if (!context.commandArgs.data['Name']) { globalError = 'Name is required.'; }" |
|||
}, |
|||
{ |
|||
"commandName": "Delete", |
|||
"type": "Pre", |
|||
"javascript": "var record = await db.get('Acme.Events.Event', context.commandArgs.entityId); if (record && record.Status === 2) { globalError = 'Completed events cannot be deleted.'; }" |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-aggregation-type.schema.json", |
|||
"title": "DashboardAggregationType", |
|||
"description": "The type of aggregation to perform. Prefer lower-case values in descriptor JSON; PascalCase aliases are accepted for compatibility.", |
|||
"markdownDescription": "`count` counts records and does not require a property. `sum`, `average`, `min`, and `max` require a property. `percentFilled` and `percentEmpty` calculate completeness for a nullable/string property.", |
|||
"type": "string", |
|||
"enum": [ |
|||
"count", |
|||
"Count", |
|||
"sum", |
|||
"Sum", |
|||
"average", |
|||
"Average", |
|||
"min", |
|||
"Min", |
|||
"max", |
|||
"Max", |
|||
"percentFilled", |
|||
"PercentFilled", |
|||
"percentEmpty", |
|||
"PercentEmpty" |
|||
], |
|||
"enumDescriptions": [ |
|||
"Count matching records.", |
|||
"Count matching records.", |
|||
"Sum a numeric property.", |
|||
"Sum a numeric property.", |
|||
"Average a numeric property.", |
|||
"Average a numeric property.", |
|||
"Minimum property value.", |
|||
"Minimum property value.", |
|||
"Maximum property value.", |
|||
"Maximum property value.", |
|||
"Percentage of records where property has a value.", |
|||
"Percentage of records where property has a value.", |
|||
"Percentage of records where property is empty/null.", |
|||
"Percentage of records where property is empty/null." |
|||
] |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-chart-descriptor.schema.json", |
|||
"title": "DashboardChartDescriptor", |
|||
"description": "Configuration for a chart visualization.", |
|||
"markdownDescription": "AI guidance: use chart visualizations for grouped aggregations. `xAxis.property` is the grouping property. `yAxis` contains one or more aggregations. Use `count` without a property; use `sum`, `average`, `min`, or `max` with a numeric/date property. Use `dateGrouping` when xAxis is date/datetime.", |
|||
"type": "object", |
|||
"properties": { |
|||
"chartType": { |
|||
"$ref": "dashboard-chart-type.schema.json", |
|||
"description": "Chart renderer type: bar, line, pie, or donut." |
|||
}, |
|||
"xAxis": { |
|||
"type": "object", |
|||
"properties": { |
|||
"property": { |
|||
"type": "string", |
|||
"description": "Property name to group by on the X-axis. Must exist on the visualization entity." |
|||
}, |
|||
"useForeignDisplay": { |
|||
"type": "boolean", |
|||
"description": "Show the FK display property instead of the raw id when xAxis.property is a foreign key.", |
|||
"default": false |
|||
}, |
|||
"dateGrouping": { |
|||
"type": ["string", "null"], |
|||
"enum": ["", "day", "week", "month", "quarter", "year", null], |
|||
"description": "Grouping interval for date/datetime properties. Omit or use empty string for no date grouping." |
|||
} |
|||
}, |
|||
"required": ["property"], |
|||
"additionalProperties": false |
|||
}, |
|||
"yAxis": { |
|||
"type": "array", |
|||
"items": { |
|||
"type": "object", |
|||
"properties": { |
|||
"aggregation": { |
|||
"$ref": "dashboard-aggregation-type.schema.json" |
|||
}, |
|||
"property": { |
|||
"type": ["string", "null"], |
|||
"description": "Property name for sum/average/min/max/percent aggregations. Omit for count." |
|||
}, |
|||
"label": { |
|||
"type": ["string", "null"], |
|||
"description": "Display label for this series. Omit to derive from aggregation/property." |
|||
}, |
|||
"color": { |
|||
"type": ["string", "null"] |
|||
} |
|||
}, |
|||
"required": ["aggregation"], |
|||
"additionalProperties": false |
|||
}, |
|||
"minItems": 1 |
|||
}, |
|||
"barOrientation": { |
|||
"type": "string", |
|||
"enum": ["vertical", "horizontal"], |
|||
"default": "vertical" |
|||
}, |
|||
"showRecordCount": { |
|||
"type": "boolean", |
|||
"default": false |
|||
}, |
|||
"maxItems": { |
|||
"type": "integer", |
|||
"description": "Maximum number of grouped items to display in the chart.", |
|||
"default": 10, |
|||
"minimum": 1, |
|||
"maximum": 50 |
|||
} |
|||
}, |
|||
"required": ["chartType", "xAxis", "yAxis"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"chartType": "bar", |
|||
"xAxis": { "property": "Status" }, |
|||
"yAxis": [{ "aggregation": "count", "label": "Events" }], |
|||
"maxItems": 10 |
|||
}, |
|||
{ |
|||
"chartType": "line", |
|||
"xAxis": { "property": "StartDate", "dateGrouping": "month" }, |
|||
"yAxis": [{ "aggregation": "sum", "property": "Budget", "label": "Budget" }] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-chart-type.schema.json", |
|||
"title": "DashboardChartType", |
|||
"description": "The type of chart. Prefer lower-case values in descriptor JSON; PascalCase aliases are accepted for compatibility.", |
|||
"markdownDescription": "Use `bar` for categorical comparisons, `line` for trends over time, `pie`/`donut` for part-of-whole views with a small number of categories.", |
|||
"type": "string", |
|||
"enum": ["bar", "Bar", "line", "Line", "pie", "Pie", "donut", "Donut"], |
|||
"enumDescriptions": [ |
|||
"Bar chart.", |
|||
"Bar chart.", |
|||
"Line chart.", |
|||
"Line chart.", |
|||
"Pie chart.", |
|||
"Pie chart.", |
|||
"Donut chart.", |
|||
"Donut chart." |
|||
] |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-descriptor.schema.json", |
|||
"title": "DashboardDescriptor", |
|||
"description": "Describes a dashboard page configuration with global filters and rows of visualizations.", |
|||
"markdownDescription": "AI guidance: a dashboard belongs to a page with `type: \"dashboard\"`. It contains one or more rows; each row contains chart, list, or numberContainer visualizations. Use visualization `entityName` to select data for chart/list items. Use numberContainer for KPI tiles, charts for grouped aggregations, and lists for recent/top records.", |
|||
"type": "object", |
|||
"properties": { |
|||
"description": { |
|||
"type": ["string", "null"], |
|||
"description": "Optional description text shown below the dashboard title." |
|||
}, |
|||
"globalFilters": { |
|||
"type": "array", |
|||
"description": "Global filters that affect visualizations, for example a date range. Visualizations can map the global date filter through globalDateFilterProperty.", |
|||
"items": { |
|||
"$ref": "dashboard-global-filter-descriptor.schema.json" |
|||
} |
|||
}, |
|||
"rows": { |
|||
"type": "array", |
|||
"description": "Dashboard rows. Each row contains one or more visualization items; width 2 items usually take the full row.", |
|||
"items": { |
|||
"$ref": "dashboard-row-descriptor.schema.json" |
|||
}, |
|||
"minItems": 1 |
|||
} |
|||
}, |
|||
"required": ["rows"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"description": "Operational overview", |
|||
"rows": [ |
|||
{ |
|||
"items": [ |
|||
{ |
|||
"name": "events-by-status", |
|||
"type": "chart", |
|||
"title": "Events by Status", |
|||
"entityName": "Acme.Events.Event", |
|||
"chart": { |
|||
"chartType": "bar", |
|||
"xAxis": { "property": "Status" }, |
|||
"yAxis": [{ "aggregation": "count", "label": "Events" }] |
|||
} |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-filter-descriptor.schema.json", |
|||
"title": "DashboardFilterDescriptor", |
|||
"description": "Static filter applied to a dashboard visualization or KPI item.", |
|||
"markdownDescription": "AI guidance: use dashboard filters to constrain the data behind a visualization, for example only active records or only records above a threshold. Each condition property must exist on the visualization/item entity. Combine conditions with `operator: \"and\"` or `operator: \"or\"`.", |
|||
"type": "object", |
|||
"properties": { |
|||
"operator": { |
|||
"type": "string", |
|||
"enum": ["and", "or"], |
|||
"default": "and" |
|||
}, |
|||
"conditions": { |
|||
"type": "array", |
|||
"items": { |
|||
"type": "object", |
|||
"properties": { |
|||
"property": { |
|||
"type": "string", |
|||
"description": "Property name to filter on. Must exist on the visualization/item entityName." |
|||
}, |
|||
"filterType": { |
|||
"type": "string", |
|||
"enum": ["equal", "notEqual", "contains", "greaterThan", "lessThan", "isNull", "isNotNull"], |
|||
"default": "equal" |
|||
}, |
|||
"value": { |
|||
"description": "Filter value. Shape depends on filterType and target property type." |
|||
} |
|||
}, |
|||
"required": ["property", "filterType"], |
|||
"additionalProperties": false |
|||
} |
|||
} |
|||
}, |
|||
"required": ["conditions"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"operator": "and", |
|||
"conditions": [ |
|||
{ "property": "Status", "filterType": "equal", "value": 1 } |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-global-filter-descriptor.schema.json", |
|||
"title": "DashboardGlobalFilterDescriptor", |
|||
"description": "Describes a dashboard-level filter control that can affect all visualizations on the dashboard.", |
|||
"markdownDescription": "AI guidance: use global filters for dashboard-wide controls, not for per-visualization conditions. The current supported type is `dateRange`; visualization filters can then reference date-like entity properties through their own `filter` or `userFilters` settings.", |
|||
"type": "object", |
|||
"properties": { |
|||
"type": { |
|||
"type": "string", |
|||
"enum": ["dateRange"], |
|||
"default": "dateRange", |
|||
"description": "Global filter control type. Currently only 'dateRange' is supported." |
|||
} |
|||
}, |
|||
"required": ["type"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ "type": "dateRange" } |
|||
] |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-list-descriptor.schema.json", |
|||
"title": "DashboardListDescriptor", |
|||
"description": "Configuration for a dashboard list/table visualization.", |
|||
"markdownDescription": "AI guidance: use list visualizations for recent records, top records, or compact operational queues. `fields` must contain property names from the parent visualization entityName. Use `sortBy` to make results deterministic.", |
|||
"type": "object", |
|||
"properties": { |
|||
"fields": { |
|||
"type": "array", |
|||
"items": { "type": "string" }, |
|||
"description": "Property names to display as columns. Each value must reference a property on the visualization entityName." |
|||
}, |
|||
"sortBy": { |
|||
"type": "object", |
|||
"properties": { |
|||
"property": { |
|||
"type": "string", |
|||
"description": "Property name to sort by. Must exist on the visualization entityName." |
|||
}, |
|||
"direction": { |
|||
"type": "string", |
|||
"enum": ["asc", "desc"], |
|||
"default": "asc" |
|||
} |
|||
}, |
|||
"required": ["property"], |
|||
"additionalProperties": false |
|||
}, |
|||
"maxRows": { |
|||
"type": "integer", |
|||
"minimum": 1, |
|||
"maximum": 50, |
|||
"default": 10 |
|||
}, |
|||
"rowHeight": { |
|||
"type": "string", |
|||
"enum": ["compact", "normal", "tall"], |
|||
"default": "compact" |
|||
}, |
|||
"colorBy": { |
|||
"oneOf": [ |
|||
{ |
|||
"type": "object", |
|||
"properties": { |
|||
"type": { |
|||
"type": "string", |
|||
"enum": ["property", "conditions"] |
|||
}, |
|||
"property": { |
|||
"type": "string", |
|||
"description": "Enum/status property name for automatic coloring." |
|||
} |
|||
}, |
|||
"required": ["type"], |
|||
"additionalProperties": false |
|||
}, |
|||
{ |
|||
"type": "null" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
"required": ["fields"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"fields": ["Title", "StartDate", "Status"], |
|||
"sortBy": { "property": "StartDate", "direction": "desc" }, |
|||
"maxRows": 10, |
|||
"rowHeight": "compact", |
|||
"colorBy": { "type": "property", "property": "Status" } |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-number-container-descriptor.schema.json", |
|||
"title": "DashboardNumberContainerDescriptor", |
|||
"description": "A container that holds multiple number/KPI items, each with its own entity and aggregation.", |
|||
"markdownDescription": "AI guidance: use numberContainer for KPI tiles. Each item chooses its own entity, aggregation, optional filter, format, and color.", |
|||
"type": "object", |
|||
"properties": { |
|||
"items": { |
|||
"type": "array", |
|||
"description": "Number/KPI items within this container. Use 1-4 items for a readable dashboard row.", |
|||
"items": { |
|||
"$ref": "dashboard-number-item-descriptor.schema.json" |
|||
} |
|||
} |
|||
}, |
|||
"required": ["items"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"items": [ |
|||
{ "name": "total-events", "title": "Total Events", "entityName": "Acme.Events.Event", "aggregation": "count", "format": "number" } |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-number-item-descriptor.schema.json", |
|||
"title": "DashboardNumberItemDescriptor", |
|||
"description": "A single number/KPI tile within a number container.", |
|||
"markdownDescription": "AI guidance: `count` does not need aggregationProperty. `sum`, `average`, `min`, `max`, `percentFilled`, and `percentEmpty` should specify aggregationProperty. Use filter to scope the KPI, for example count only active records.", |
|||
"type": "object", |
|||
"properties": { |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Unique identifier for this number item. Prefer kebab-case such as 'total-events'.", |
|||
"minLength": 1 |
|||
}, |
|||
"title": { |
|||
"type": "string", |
|||
"description": "Display title for this KPI." |
|||
}, |
|||
"entityName": { |
|||
"type": "string", |
|||
"description": "Entity this number item sources data from. Must match an entity descriptor or known reference entity.", |
|||
"minLength": 1 |
|||
}, |
|||
"aggregation": { |
|||
"$ref": "dashboard-aggregation-type.schema.json", |
|||
"description": "Aggregation used to compute the KPI value." |
|||
}, |
|||
"aggregationProperty": { |
|||
"type": "string", |
|||
"description": "Property name for sum/average/min/max/percent aggregations. Omit for count." |
|||
}, |
|||
"format": { |
|||
"type": "string", |
|||
"enum": ["number", "currency", "percentage"], |
|||
"default": "number" |
|||
}, |
|||
"color": { |
|||
"type": "string", |
|||
"description": "Display color name (blue, green, red, purple, orange, indigo, amber, teal)" |
|||
}, |
|||
"globalDateFilterProperty": { |
|||
"type": "string", |
|||
"description": "Date/DateTime property used to apply dashboard global date filters. Defaults to CreationTime when omitted." |
|||
}, |
|||
"filter": { |
|||
"oneOf": [ |
|||
{ "$ref": "dashboard-filter-descriptor.schema.json" }, |
|||
{ "type": "null" } |
|||
] |
|||
}, |
|||
"clickToSeeRecords": { |
|||
"type": "boolean", |
|||
"description": "Allow users to click to see underlying records when supported by the runtime.", |
|||
"default": false |
|||
} |
|||
}, |
|||
"required": ["name", "title", "entityName", "aggregation"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "total-events", |
|||
"title": "Total Events", |
|||
"entityName": "Acme.Events.Event", |
|||
"aggregation": "count", |
|||
"format": "number", |
|||
"color": "blue" |
|||
}, |
|||
{ |
|||
"name": "total-budget", |
|||
"title": "Total Budget", |
|||
"entityName": "Acme.Events.Event", |
|||
"aggregation": "sum", |
|||
"aggregationProperty": "Budget", |
|||
"format": "currency", |
|||
"color": "green" |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-row-descriptor.schema.json", |
|||
"title": "DashboardRowDescriptor", |
|||
"description": "Describes one row in the dashboard layout grid. Each row contains one or two visualization items.", |
|||
"markdownDescription": "AI guidance: organize dashboard visualizations into rows by visual importance. Use one item for a full-width chart/list/number panel, and two items when related visualizations should be displayed side by side. Do not put more than two visualizations in a row; create another row instead.", |
|||
"type": "object", |
|||
"properties": { |
|||
"items": { |
|||
"type": "array", |
|||
"description": "Visualization items in this row. Use one item for full-width content or two items for a two-column row.", |
|||
"items": { |
|||
"$ref": "dashboard-visualization-descriptor.schema.json" |
|||
}, |
|||
"minItems": 1, |
|||
"maxItems": 2 |
|||
} |
|||
}, |
|||
"required": ["items"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"items": [ |
|||
{ |
|||
"name": "overview", |
|||
"type": "numberContainer", |
|||
"title": "Overview", |
|||
"numberContainer": { |
|||
"items": [ |
|||
{ "name": "total-records", "title": "Total Records", "entityName": "Acme.Events.Event", "aggregation": "count" } |
|||
] |
|||
} |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-visualization-descriptor.schema.json", |
|||
"title": "DashboardVisualizationDescriptor", |
|||
"description": "A single visualization element in a dashboard row: chart, list, or number container.", |
|||
"markdownDescription": "AI guidance: set `type` and then provide the matching payload: `chart` for type chart, `list` for type list, or `numberContainer` for type numberContainer. Do not populate unrelated payloads. Chart and list visualizations require `entityName`; number containers define entityName per KPI item.", |
|||
"type": "object", |
|||
"properties": { |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Unique identifier within the dashboard. Prefer kebab-case such as 'events-by-status'.", |
|||
"minLength": 1 |
|||
}, |
|||
"type": { |
|||
"$ref": "dashboard-visualization-type.schema.json", |
|||
"description": "Visualization renderer type. Determines which payload property must be populated." |
|||
}, |
|||
"title": { |
|||
"type": "string", |
|||
"description": "Display title for the visualization." |
|||
}, |
|||
"description": { |
|||
"type": ["string", "null"], |
|||
"description": "Optional description text. Can be shown inline or as tooltip depending on showDescriptionAsTooltip." |
|||
}, |
|||
"width": { |
|||
"type": "integer", |
|||
"enum": [1, 2], |
|||
"default": 1, |
|||
"description": "Column width: 1 = half row, 2 = full row." |
|||
}, |
|||
"entityName": { |
|||
"type": "string", |
|||
"description": "Entity this visualization sources data from. Required for chart and list visualizations; numberContainer items define their own entityName." |
|||
}, |
|||
"globalDateFilterProperty": { |
|||
"type": "string", |
|||
"description": "Date/DateTime property used to apply dashboard global date filters to this visualization. Defaults to CreationTime when omitted." |
|||
}, |
|||
"filter": { |
|||
"oneOf": [ |
|||
{ "$ref": "dashboard-filter-descriptor.schema.json" }, |
|||
{ "type": "null" } |
|||
] |
|||
}, |
|||
"userFilters": { |
|||
"type": "array", |
|||
"description": "Interactive filters exposed to end users on this visualization.", |
|||
"items": { |
|||
"type": "object", |
|||
"properties": { |
|||
"property": { |
|||
"type": "string", |
|||
"description": "Property name to filter on. Must exist on entityName.", |
|||
"minLength": 1 |
|||
} |
|||
}, |
|||
"required": ["property"], |
|||
"additionalProperties": false |
|||
} |
|||
}, |
|||
"showDescriptionAsTooltip": { |
|||
"type": "boolean", |
|||
"default": false |
|||
}, |
|||
"clickToSeeRecords": { |
|||
"type": "boolean", |
|||
"description": "Allow users to click to see underlying records when supported by the runtime.", |
|||
"default": false |
|||
}, |
|||
"chart": { |
|||
"oneOf": [ |
|||
{ "$ref": "dashboard-chart-descriptor.schema.json" }, |
|||
{ "type": "null" } |
|||
] |
|||
}, |
|||
"list": { |
|||
"oneOf": [ |
|||
{ "$ref": "dashboard-list-descriptor.schema.json" }, |
|||
{ "type": "null" } |
|||
] |
|||
}, |
|||
"numberContainer": { |
|||
"oneOf": [ |
|||
{ "$ref": "dashboard-number-container-descriptor.schema.json" }, |
|||
{ "type": "null" } |
|||
] |
|||
} |
|||
}, |
|||
"required": ["name", "type", "title"], |
|||
"allOf": [ |
|||
{ |
|||
"if": { |
|||
"required": ["type"], |
|||
"properties": { |
|||
"type": { "enum": ["chart", "Chart"] } |
|||
} |
|||
}, |
|||
"then": { |
|||
"required": ["chart", "entityName"], |
|||
"properties": { |
|||
"chart": { "$ref": "dashboard-chart-descriptor.schema.json" }, |
|||
"entityName": { |
|||
"type": "string", |
|||
"minLength": 1 |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"if": { |
|||
"required": ["type"], |
|||
"properties": { |
|||
"type": { "enum": ["list", "List"] } |
|||
} |
|||
}, |
|||
"then": { |
|||
"required": ["list", "entityName"], |
|||
"properties": { |
|||
"list": { "$ref": "dashboard-list-descriptor.schema.json" }, |
|||
"entityName": { |
|||
"type": "string", |
|||
"minLength": 1 |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"if": { |
|||
"required": ["type"], |
|||
"properties": { |
|||
"type": { "enum": ["numberContainer", "NumberContainer"] } |
|||
} |
|||
}, |
|||
"then": { |
|||
"required": ["numberContainer"], |
|||
"properties": { |
|||
"numberContainer": { "$ref": "dashboard-number-container-descriptor.schema.json" } |
|||
} |
|||
} |
|||
} |
|||
], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "events-by-status", |
|||
"type": "chart", |
|||
"title": "Events by Status", |
|||
"entityName": "Acme.Events.Event", |
|||
"chart": { |
|||
"chartType": "bar", |
|||
"xAxis": { "property": "Status" }, |
|||
"yAxis": [{ "aggregation": "count", "label": "Events" }] |
|||
} |
|||
}, |
|||
{ |
|||
"name": "recent-events", |
|||
"type": "list", |
|||
"title": "Recent Events", |
|||
"entityName": "Acme.Events.Event", |
|||
"list": { |
|||
"fields": ["Title", "StartDate", "Status"], |
|||
"sortBy": { "property": "StartDate", "direction": "desc" }, |
|||
"maxRows": 10 |
|||
} |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "dashboard-visualization-type.schema.json", |
|||
"title": "DashboardVisualizationType", |
|||
"description": "The type of dashboard visualization. Prefer lower-case values in descriptor JSON; PascalCase aliases are accepted for compatibility.", |
|||
"markdownDescription": "`chart` groups/aggregates entity data into a bar/line/pie/donut chart. `list` displays recent/top records from one entity. `numberContainer` displays one or more KPI number tiles, each with its own entity and aggregation.", |
|||
"type": "string", |
|||
"enum": ["chart", "Chart", "list", "List", "numberContainer", "NumberContainer"], |
|||
"enumDescriptions": [ |
|||
"Chart visualization with x/y axis aggregation.", |
|||
"Chart visualization with x/y axis aggregation.", |
|||
"Record list visualization.", |
|||
"Record list visualization.", |
|||
"KPI number/tile container.", |
|||
"KPI number/tile container." |
|||
] |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "endpoint-descriptor.schema.json", |
|||
"title": "Custom Endpoint Descriptor", |
|||
"description": "Defines a custom HTTP endpoint that executes server-side JavaScript code.", |
|||
"markdownDescription": "AI guidance: use custom endpoints for model-owned actions and lightweight APIs. `name` must be unique. `route` should start with `/api/` and must not conflict with another route/method. Use `{id}` style path parameters when needed. Scripts can access request data through the endpoint context and return HTTP results with helpers such as `context.ok(value)`, `context.created(value)`, and `context.noContent()` where available. Require authentication by default and add `requiredPermissions` for protected operations.", |
|||
"type": "object", |
|||
"properties": { |
|||
"$schema": { |
|||
"type": "string", |
|||
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file." |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Unique endpoint identifier used by designer/model health. Prefer PascalCase or kebab-case, for example 'SearchCustomers'.", |
|||
"minLength": 1 |
|||
}, |
|||
"route": { |
|||
"type": "string", |
|||
"description": "URL route pattern. Must start with '/' and should use an application-specific prefix such as '/api/low-code/events/{id}'. Route parameters use ASP.NET style braces, for example '{id}'.", |
|||
"minLength": 1, |
|||
"pattern": "^/" |
|||
}, |
|||
"method": { |
|||
"type": "string", |
|||
"description": "HTTP method. GET should be read-only; POST/PUT/PATCH/DELETE may mutate state.", |
|||
"enum": ["GET", "POST", "PUT", "DELETE", "PATCH"], |
|||
"default": "GET" |
|||
}, |
|||
"javascript": { |
|||
"type": "string", |
|||
"description": "JavaScript code to execute. Use context request/response helpers and services exposed by the host, such as db, currentUser/currentTenant, authorization, emailSender, config, http, event bus, background jobs, and logging helpers.", |
|||
"minLength": 1 |
|||
}, |
|||
"requireAuthentication": { |
|||
"type": "boolean", |
|||
"description": "Whether authentication is required. Keep true unless this endpoint is intentionally public.", |
|||
"default": true |
|||
}, |
|||
"requiredPermissions": { |
|||
"type": "array", |
|||
"description": "Permission names required to access the endpoint. Checked only when authentication is required. Values should reference custom permissions or known static permissions.", |
|||
"items": { |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"description": { |
|||
"type": "string", |
|||
"description": "Optional human-readable description for designer documentation and model health context." |
|||
} |
|||
}, |
|||
"required": ["name", "route", "javascript"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "SearchCustomers", |
|||
"route": "/api/low-code/customers/search", |
|||
"method": "GET", |
|||
"requireAuthentication": true, |
|||
"requiredPermissions": ["Acme.Customers.View"], |
|||
"javascript": "var q = context.request.query.q || ''; var table = await db.query('Acme.Crm.Customer'); var rows = await table.where(c => c.Name.toLowerCase().includes(q.toLowerCase())).take(10).toList(); return context.ok(rows);" |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "entity-attachment-descriptor.schema.json", |
|||
"title": "EntityAttachmentDescriptor", |
|||
"description": "Configures record-level attachments for an entity.", |
|||
"markdownDescription": "AI guidance: use entity attachments when records need a collection of arbitrary files. Use File/Image entity properties instead when the file is a named business field such as `ContractPdf` or `CoverImage`.", |
|||
"type": "object", |
|||
"properties": { |
|||
"isEnabled": { |
|||
"type": "boolean", |
|||
"description": "When true, records of this entity can have attachments." |
|||
}, |
|||
"maxFileCount": { |
|||
"type": "integer", |
|||
"description": "Maximum number of files allowed per entity record.", |
|||
"minimum": 1 |
|||
}, |
|||
"maxFileSizeBytes": { |
|||
"type": "integer", |
|||
"description": "Maximum allowed size in bytes for a single attachment.", |
|||
"minimum": 1 |
|||
}, |
|||
"maxTotalSizeBytes": { |
|||
"type": "integer", |
|||
"description": "Maximum total attachment size in bytes per entity record.", |
|||
"minimum": 1 |
|||
}, |
|||
"allowedContentTypes": { |
|||
"type": "array", |
|||
"description": "Allowed MIME content types, wildcard MIME patterns, or file extensions. Examples: 'image/*', 'application/pdf', '.docx'.", |
|||
"items": { |
|||
"type": "string", |
|||
"minLength": 1 |
|||
} |
|||
} |
|||
}, |
|||
"required": ["isEnabled"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"isEnabled": true, |
|||
"maxFileCount": 5, |
|||
"maxFileSizeBytes": 5242880, |
|||
"allowedContentTypes": ["image/*", "application/pdf"] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "entity-cross-field-validation-descriptor.schema.json", |
|||
"title": "EntityCrossFieldValidationDescriptor", |
|||
"description": "Describes an entity-level validation rule that compares one property against another property on the same entity.", |
|||
"markdownDescription": "AI guidance: use cross-field validations for rules such as EndDate > StartDate, Min <= Max, PasswordRepeat == Password, or PublishedOn >= CreatedOn. `propertyName` receives the validation error; `otherPropertyName` is the comparison target. Both must exist on the entity.", |
|||
"type": "object", |
|||
"properties": { |
|||
"propertyName": { |
|||
"type": "string", |
|||
"description": "Property that receives the validation error when the rule fails. Must exist on the entity.", |
|||
"minLength": 1 |
|||
}, |
|||
"operator": { |
|||
"type": "string", |
|||
"description": "Comparison operator", |
|||
"enum": [ |
|||
"equals", |
|||
"notEquals", |
|||
"greaterThan", |
|||
"greaterThanOrEqual", |
|||
"lessThan", |
|||
"lessThanOrEqual" |
|||
] |
|||
}, |
|||
"otherPropertyName": { |
|||
"type": "string", |
|||
"description": "Property to compare against. Must exist on the same entity.", |
|||
"minLength": 1 |
|||
}, |
|||
"message": { |
|||
"type": "string", |
|||
"description": "Optional custom validation message" |
|||
} |
|||
}, |
|||
"required": ["propertyName", "operator", "otherPropertyName"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"propertyName": "EndDate", |
|||
"operator": "greaterThan", |
|||
"otherPropertyName": "StartDate", |
|||
"message": "End Date must be greater than Start Date." |
|||
}, |
|||
{ |
|||
"propertyName": "PasswordRepeat", |
|||
"operator": "equals", |
|||
"otherPropertyName": "Password", |
|||
"message": "Password repeat must match Password." |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "entity-descriptor.schema.json", |
|||
"title": "EntityDescriptor", |
|||
"description": "Describes a dynamic entity. An entity is the runtime data model: it defines a table-like aggregate, its properties, validations, parent-child relation, attachment support, and create/update/delete interceptors.", |
|||
"markdownDescription": "AI guidance: create one entity per aggregate/root concept. Use a stable namespace-style `name` such as `Acme.Crm.Customer`; this name is referenced by pages, forms, foreign keys, dashboards, scripts, and permissions. Put user-facing labels in `displayName`; choose `displayProperty` as a short string property used by lookups. Use `parent` only for child/detail entities that belong to a parent record.", |
|||
"type": "object", |
|||
"properties": { |
|||
"$schema": { |
|||
"type": "string", |
|||
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file." |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Stable full name of the entity, usually Namespace.Module.EntityName (for example 'Acme.Crm.Customer'). Must be unique across all model layers. Do not rename after data exists unless a migration/rename flow is intended.", |
|||
"minLength": 1 |
|||
}, |
|||
"displayName": { |
|||
"type": "string", |
|||
"description": "Default plural/screen label for this entity (for example 'Customers'). Page menu titles are configured separately on page descriptors.", |
|||
"minLength": 1 |
|||
}, |
|||
"displayProperty": { |
|||
"type": "string", |
|||
"description": "Property name used when another entity references this entity in a lookup/autocomplete. Prefer a required string property such as 'Name', 'Title', or 'Code'." |
|||
}, |
|||
"parent": { |
|||
"type": "string", |
|||
"description": "Full name of the parent entity for parent-child/detail entities. When set, records of this entity are scoped under the parent and should include an FK property to the parent.", |
|||
"minLength": 1 |
|||
}, |
|||
"attachments": { |
|||
"$ref": "entity-attachment-descriptor.schema.json", |
|||
"description": "Record-level attachment settings. Use this for multiple arbitrary files attached to a record; use File/Image properties for first-class file fields." |
|||
}, |
|||
"properties": { |
|||
"type": "array", |
|||
"description": "Entity properties. Omit framework audit/id fields such as Id, CreationTime, CreatorId, LastModificationTime, IsDeleted unless intentionally overriding metadata; the runtime supplies standard fields.", |
|||
"items": { |
|||
"$ref": "entity-property-descriptor.schema.json" |
|||
} |
|||
}, |
|||
"crossFieldValidations": { |
|||
"type": "array", |
|||
"description": "Entity-level validation rules that compare two properties from the same entity. Use for date ranges, matching password fields, numeric min/max pairs, and other cross-field rules.", |
|||
"items": { |
|||
"$ref": "entity-cross-field-validation-descriptor.schema.json" |
|||
} |
|||
}, |
|||
"interceptors": { |
|||
"type": "array", |
|||
"description": "Create/Update/Delete command interceptors. Use Pre interceptors to validate, normalize, or block a command; use Post interceptors for side effects after persistence.", |
|||
"items": { |
|||
"$ref": "command-interceptor-descriptor.schema.json" |
|||
} |
|||
} |
|||
}, |
|||
"required": ["name"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "Acme.Crm.Customer", |
|||
"displayName": "Customers", |
|||
"displayProperty": "Name", |
|||
"properties": [ |
|||
{ "name": "Name", "type": "string", "isRequired": true }, |
|||
{ "name": "Email", "type": "string", "validators": [{ "type": "email" }] } |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "entity-property-descriptor.schema.json", |
|||
"title": "EntityPropertyDescriptor", |
|||
"description": "Describes one dynamic entity property. Properties define persisted data fields, enum fields, foreign keys, upload fields, server-only fields, defaults, uniqueness, and validators.", |
|||
"markdownDescription": "AI guidance: use PascalCase property names. Use `type` for primitive fields, `type: \"enum\"` with `enumType` for enum fields, and a `foreignKey` object for lookup fields. FK property names should normally end with `Id`. Keep sensitive values `serverOnly: true`. Do not add legacy UI configuration here; page columns/filters and form fields own UI behavior.", |
|||
"type": "object", |
|||
"properties": { |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Stable PascalCase property name, unique within the entity. Examples: 'Name', 'EmailAddress', 'CustomerId', 'StartDate'.", |
|||
"minLength": 1 |
|||
}, |
|||
"type": { |
|||
"$ref": "entity-property-type.schema.json", |
|||
"description": "Primitive or special property type. If omitted, runtime treats the property as string. Use 'enum' only with enumType; use 'file'/'image' for first-class upload fields." |
|||
}, |
|||
"displayName": { |
|||
"type": "string", |
|||
"description": "Default display label for this property. Page columns and form fields can override it. Omit when the label can be derived from the property name.", |
|||
"minLength": 1 |
|||
}, |
|||
"enumType": { |
|||
"type": "string", |
|||
"description": "Name of a JSON-defined enum from top-level enums or a full type name for code enums. Required when type is 'enum'." |
|||
}, |
|||
"allowSetByClients": { |
|||
"type": "boolean", |
|||
"description": "Controls whether create/update clients may set the property. Use false for server-computed or protected fields that can still be returned to clients." |
|||
}, |
|||
"serverOnly": { |
|||
"type": "boolean", |
|||
"description": "When true, this property is completely hidden from clients, API responses, and UI definitions. Use for secrets, internal notes, hashes, or backend-only workflow state." |
|||
}, |
|||
"isMappedToDbField": { |
|||
"type": "boolean", |
|||
"description": "Whether this property is mapped to a database column. Keep true or omit for normal persisted fields. Use false for computed/transient fields supplied by scripts or backend logic." |
|||
}, |
|||
"defaultValue": { |
|||
"type": ["string", "null"], |
|||
"description": "Default value for new records. Stored as a string in descriptor JSON and converted to the declared property type at runtime. Examples: '0' for int/enum, 'true' for boolean, '2026-05-18T09:00:00Z' for datetime." |
|||
}, |
|||
"isUnique": { |
|||
"type": "boolean", |
|||
"description": "Whether this property value must be unique across records of this entity. Use for codes, slugs, natural keys, and names only when duplicates are not allowed." |
|||
}, |
|||
"isRequired": { |
|||
"type": "boolean", |
|||
"description": "When true, the property is required/not nullable. This affects database schema, backend validation, and generated UI validation. Existing data may need defaults before turning this on." |
|||
}, |
|||
"foreignKey": { |
|||
"$ref": "foreign-key-descriptor.schema.json", |
|||
"description": "Foreign key/lookup relation. The current property's value stores the referenced entity id. The property name should usually be '<ReferencedEntity>NameId', for example 'CustomerId'." |
|||
}, |
|||
"fileMaxSizeBytes": { |
|||
"type": "integer", |
|||
"description": "Maximum allowed file size in bytes for File and Image properties. Example: 5242880 for 5 MiB.", |
|||
"minimum": 1 |
|||
}, |
|||
"fileAllowedContentTypes": { |
|||
"type": "array", |
|||
"description": "Allowed MIME content types or wildcard patterns for File and Image properties. Examples: 'image/*', 'application/pdf'.", |
|||
"items": { |
|||
"type": "string", |
|||
"minLength": 1 |
|||
} |
|||
}, |
|||
"imageMaxWidth": { |
|||
"type": "integer", |
|||
"description": "Optional maximum image width in pixels.", |
|||
"minimum": 1 |
|||
}, |
|||
"imageMaxHeight": { |
|||
"type": "integer", |
|||
"description": "Optional maximum image height in pixels.", |
|||
"minimum": 1 |
|||
}, |
|||
"imageResizeMode": { |
|||
"type": "string", |
|||
"description": "How uploaded images should be resized when image dimensions are configured. 'fit' preserves the full image inside the bounds; 'fill' crops/fills the target bounds.", |
|||
"enum": ["fit", "fill"] |
|||
}, |
|||
"validators": { |
|||
"type": "array", |
|||
"description": "Backend/UI validators for this property. Use required, length, range, pattern, email, phone, url, or creditCard as appropriate. Duplicate required can be omitted when isRequired is true unless a custom message is needed.", |
|||
"items": { |
|||
"$ref": "validator-descriptor.schema.json" |
|||
} |
|||
} |
|||
}, |
|||
"required": [ |
|||
"name" |
|||
], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ "name": "Name", "type": "string", "isRequired": true, "validators": [{ "type": "maxLength", "length": 128 }] }, |
|||
{ "name": "Status", "type": "enum", "enumType": "Acme.Events.EventStatus", "defaultValue": "0" }, |
|||
{ "name": "CustomerId", "foreignKey": { "entityName": "Acme.Crm.Customer", "displayPropertyName": "Name" } }, |
|||
{ "name": "CoverImage", "type": "image", "fileAllowedContentTypes": ["image/*"], "fileMaxSizeBytes": 5242880, "imageResizeMode": "fit" } |
|||
] |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "entity-property-type.schema.json", |
|||
"title": "EntityPropertyType", |
|||
"description": "Data type of an entity property. Use canonical lowercase values for new descriptors and MCP mutations; compatibility aliases are accepted for older descriptors.", |
|||
"markdownDescription": "Use canonical lowercase values in MCP/write payloads: `string`, `int`, `long`, `decimal`, `datetime`, `boolean`, `guid`, `enum`, `date`, `time`, `file`, `image`, or `money`. `string` stores text; `int`/`long` store whole numbers; `decimal` and `money` store numeric decimals; `datetime`, `date`, and `time` model temporal values; `boolean` stores true/false; `guid` stores GUID values; `enum` requires `enumType`; `file` and `image` store upload metadata/content through the low-code file pipeline. Legacy aliases such as `String`, `Int`, `dateTime`, and `DateTime` remain accepted by the schema for compatibility, but new payloads should not use .NET aliases like `Int32`.", |
|||
"type": "string", |
|||
"enum": [ |
|||
"string", |
|||
"String", |
|||
"int", |
|||
"Int", |
|||
"long", |
|||
"Long", |
|||
"decimal", |
|||
"Decimal", |
|||
"datetime", |
|||
"dateTime", |
|||
"DateTime", |
|||
"boolean", |
|||
"Boolean", |
|||
"guid", |
|||
"Guid", |
|||
"enum", |
|||
"Enum", |
|||
"date", |
|||
"Date", |
|||
"time", |
|||
"Time", |
|||
"file", |
|||
"File", |
|||
"image", |
|||
"Image", |
|||
"money", |
|||
"Money" |
|||
], |
|||
"enumDescriptions": [ |
|||
"Text/string value.", |
|||
"Text/string value.", |
|||
"32-bit whole number.", |
|||
"32-bit whole number.", |
|||
"64-bit whole number.", |
|||
"64-bit whole number.", |
|||
"Decimal numeric value.", |
|||
"Decimal numeric value.", |
|||
"Date and time value.", |
|||
"Date and time value.", |
|||
"Date and time value.", |
|||
"Boolean true/false value.", |
|||
"Boolean true/false value.", |
|||
"GUID/UUID value.", |
|||
"GUID/UUID value.", |
|||
"Integer-backed enum value; set enumType.", |
|||
"Integer-backed enum value; set enumType.", |
|||
"Date-only value.", |
|||
"Date-only value.", |
|||
"Time-only value.", |
|||
"Time-only value.", |
|||
"Uploaded file field.", |
|||
"Uploaded file field.", |
|||
"Uploaded image field with optional dimension constraints.", |
|||
"Uploaded image field with optional dimension constraints.", |
|||
"Money amount rendered with money-aware controls.", |
|||
"Money amount rendered with money-aware controls." |
|||
] |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "enum-descriptor.schema.json", |
|||
"title": "EnumDescriptor", |
|||
"description": "Describes an integer-backed enum definition for use in entity properties and select/kanban UI.", |
|||
"markdownDescription": "AI guidance: define enums before entity properties that reference them. Use a stable namespace-style `name` such as `Acme.Events.EventStatus`. Each value name should be PascalCase and unique within the enum. Use explicit integer `value`s for stable persistence; do not reorder or renumber after data exists.", |
|||
"type": "object", |
|||
"properties": { |
|||
"$schema": { |
|||
"type": "string", |
|||
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file." |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Stable unique enum name. Use a namespace-style name if the enum belongs to a module/domain, for example 'Acme.Events.EventStatus'.", |
|||
"minLength": 1 |
|||
}, |
|||
"values": { |
|||
"type": "array", |
|||
"description": "Ordered list of integer-backed enum values. Values are stored as integers; names are used for display and model readability.", |
|||
"items": { |
|||
"type": "object", |
|||
"properties": { |
|||
"name": { |
|||
"type": "string", |
|||
"description": "PascalCase enum value name, for example 'Draft', 'Scheduled', or 'Completed'." |
|||
}, |
|||
"value": { |
|||
"type": "integer", |
|||
"description": "Stable integer value. Prefer explicit values starting at 0 so generated descriptor JSON is deterministic." |
|||
} |
|||
}, |
|||
"required": [ |
|||
"name" |
|||
], |
|||
"additionalProperties": false |
|||
}, |
|||
"minItems": 1 |
|||
} |
|||
}, |
|||
"required": [ |
|||
"name", |
|||
"values" |
|||
], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "Acme.Events.EventStatus", |
|||
"values": [ |
|||
{ "name": "Draft", "value": 0 }, |
|||
{ "name": "Scheduled", "value": 1 }, |
|||
{ "name": "Completed", "value": 2 } |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "foreign-key-descriptor.schema.json", |
|||
"title": "ForeignKeyDescriptor", |
|||
"description": "Describes a foreign key/lookup relationship from the owning entity property to another entity or reference entity.", |
|||
"markdownDescription": "AI guidance: put `foreignKey` on the property that stores the related record id. The property name should usually end with `Id`, for example `CustomerId`. `entityName` must match a model entity name or a registered code/reference entity such as `Volo.Abp.Identity.IdentityUser`. Use `displayPropertyName` when the target display field is not obvious. Use `dependsOn` for cascading dropdowns such as City filtered by Country. Use `access` only for reverse access from the referenced entity side: `none`, `view`, or `edit`. Do not use `lookup` as `access`; lookup is a form field/control type.", |
|||
"type": "object", |
|||
"properties": { |
|||
"entityName": { |
|||
"type": "string", |
|||
"description": "Full name of the related entity or registered reference entity. Must match an entity descriptor name or a known code/reference entity.", |
|||
"minLength": 1 |
|||
}, |
|||
"displayPropertyName": { |
|||
"type": "string", |
|||
"description": "Property name to display from the related entity in lookups/autocomplete. Omit to use the target entity displayProperty.", |
|||
"minLength": 1 |
|||
}, |
|||
"access": { |
|||
"type": "string", |
|||
"description": "Access level for managing this relation from the referenced entity side. 'none' means no reverse access; 'view' allows the referenced entity page to show related records; 'edit' allows managing related records from the referenced side.", |
|||
"enum": ["none", "None", "view", "View", "edit", "Edit"], |
|||
"default": "none" |
|||
}, |
|||
"dependsOn": { |
|||
"type": "object", |
|||
"description": "Cascading dependency: filter this FK lookup by the value of another FK property on the same owning entity. Example: CityId depends on CountryId and filters City.CountryId.", |
|||
"properties": { |
|||
"propertyName": { |
|||
"type": "string", |
|||
"description": "Property name on the owning entity whose value provides the filter (for example 'CountryId' on an Address entity).", |
|||
"minLength": 1 |
|||
}, |
|||
"filterPropertyName": { |
|||
"type": "string", |
|||
"description": "Property name on the target lookup entity to filter by (for example 'CountryId' on City).", |
|||
"minLength": 1 |
|||
} |
|||
}, |
|||
"required": ["propertyName", "filterPropertyName"], |
|||
"additionalProperties": false |
|||
} |
|||
}, |
|||
"required": ["entityName"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"entityName": "Acme.Crm.Customer", |
|||
"displayPropertyName": "Name" |
|||
}, |
|||
{ |
|||
"entityName": "Acme.Geo.City", |
|||
"displayPropertyName": "Name", |
|||
"dependsOn": { |
|||
"propertyName": "CountryId", |
|||
"filterPropertyName": "CountryId" |
|||
} |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "form-descriptor.schema.json", |
|||
"title": "FormDescriptor", |
|||
"description": "Describes a named create/edit form definition bound to one entity.", |
|||
"markdownDescription": "AI guidance: a form is referenced by page `formName`, `createFormName`, or `editFormName`. Keep `entityName` aligned with the page entityName. Define all fields in `fields`, then place every visible field in `layout.tabs[].groups[].rows[].cells[]` by field id. Use form `rules` for conditional visibility/enabled state; use entity validators for core data validation.", |
|||
"type": "object", |
|||
"properties": { |
|||
"$schema": { |
|||
"type": "string", |
|||
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file." |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Stable unique form identifier. Prefer kebab-case, for example 'customer-form' or 'event-form'. Pages reference this value.", |
|||
"minLength": 1 |
|||
}, |
|||
"entityName": { |
|||
"type": "string", |
|||
"description": "Full name of the entity this form is bound to. Must match the page entityName that uses this form.", |
|||
"minLength": 1 |
|||
}, |
|||
"enableSaveAndNew": { |
|||
"type": "boolean", |
|||
"description": "Whether the form should expose a Save and New action in addition to the standard save action. Useful for rapid data entry.", |
|||
"default": false |
|||
}, |
|||
"fields": { |
|||
"type": "array", |
|||
"description": "Flat list of all fields in this form. Field ids must be unique within the form; bound fields should point to properties on entityName.", |
|||
"items": { |
|||
"$ref": "form-field-descriptor.schema.json" |
|||
} |
|||
}, |
|||
"layout": { |
|||
"$ref": "form-layout-descriptor.schema.json", |
|||
"description": "Visual layout for the fields. Every layout cell fieldId must refer to a field in fields." |
|||
}, |
|||
"rules": { |
|||
"type": "array", |
|||
"description": "Conditional rules for field/group visibility, enabled state, and value setting. Use for simple client-side form behavior.", |
|||
"items": { |
|||
"$ref": "form-rule-descriptor.schema.json" |
|||
} |
|||
} |
|||
}, |
|||
"required": ["name", "entityName", "fields", "layout"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "event-form", |
|||
"entityName": "Acme.Events.Event", |
|||
"fields": [ |
|||
{ "id": "title", "label": "Title", "type": "text", "binding": "Title" }, |
|||
{ "id": "status", "label": "Status", "type": "select", "binding": "Status", "enumType": "Acme.Events.EventStatus" } |
|||
], |
|||
"layout": { |
|||
"tabs": [ |
|||
{ |
|||
"id": "main", |
|||
"title": "Main", |
|||
"isDefault": true, |
|||
"groups": [ |
|||
{ |
|||
"id": "details", |
|||
"title": "Details", |
|||
"isDefault": true, |
|||
"rows": [ |
|||
{ "cells": [{ "fieldId": "title", "colSpan": 4 }] }, |
|||
{ "cells": [{ "fieldId": "status", "colSpan": 2 }] } |
|||
] |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "form-field-descriptor.schema.json", |
|||
"title": "FormFieldDescriptor", |
|||
"description": "Describes a single field in a form. A field may be bound to an entity property or unbound for computed/display-only UI.", |
|||
"markdownDescription": "AI guidance: use a stable camelCase `id` for each field. For ordinary data entry, set `binding` to an entity property and choose a field `type` compatible with that property. For enum selects, set `type: \"select\"` and `enumType`. For FK lookups, use `type: \"lookup\"` and bind to the FK property. Put fields into layout cells by id.", |
|||
"type": "object", |
|||
"properties": { |
|||
"id": { |
|||
"type": "string", |
|||
"description": "Unique identifier for this field within the form. Prefer camelCase, for example 'title' or 'customerId'. Layout cells and rules reference this id.", |
|||
"minLength": 1 |
|||
}, |
|||
"label": { |
|||
"type": "string", |
|||
"description": "Display label for the field.", |
|||
"minLength": 1 |
|||
}, |
|||
"type": { |
|||
"$ref": "form-field-type.schema.json", |
|||
"description": "Visual/input control type. Choose a type compatible with the bound entity property." |
|||
}, |
|||
"binding": { |
|||
"type": ["string", "null"], |
|||
"description": "Entity property name to bind to, or null/omitted for unbound fields. Supports dotted paths like 'Parent.Name' for related entity display where supported." |
|||
}, |
|||
"enumType": { |
|||
"type": "string", |
|||
"description": "Enum name for select fields bound to enum properties. Must match the entity property's enumType or a known code enum." |
|||
}, |
|||
"defaultValue": { |
|||
"description": "Default field value used by the UI when creating a new record. Prefer entity property defaultValue for persisted defaults." |
|||
}, |
|||
"placeholder": { |
|||
"type": "string", |
|||
"description": "Placeholder text for the input" |
|||
}, |
|||
"helpText": { |
|||
"type": "string", |
|||
"description": "Help text displayed below the field" |
|||
}, |
|||
"readOnly": { |
|||
"type": "boolean", |
|||
"description": "Whether the field is read-only in the form UI. This does not by itself protect backend writes; use allowSetByClients/serverOnly for security.", |
|||
"default": false |
|||
}, |
|||
"modeVisibility": { |
|||
"type": "string", |
|||
"enum": ["both", "Both", "createOnly", "CreateOnly", "editOnly", "EditOnly"], |
|||
"description": "Controls in which form mode the field is visible: both, createOnly, or editOnly.", |
|||
"default": "both" |
|||
}, |
|||
"validations": { |
|||
"type": "array", |
|||
"description": "Form-level validation rules that supplement entity-level validators. Use when validation is specific to this form.", |
|||
"items": { |
|||
"$ref": "validator-descriptor.schema.json" |
|||
} |
|||
} |
|||
}, |
|||
"required": ["id", "label", "type"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ "id": "title", "label": "Title", "type": "text", "binding": "Title", "validations": [{ "type": "required" }] }, |
|||
{ "id": "status", "label": "Status", "type": "select", "binding": "Status", "enumType": "Acme.Events.EventStatus" }, |
|||
{ "id": "customerId", "label": "Customer", "type": "lookup", "binding": "CustomerId" } |
|||
] |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "form-field-type.schema.json", |
|||
"title": "FormFieldType", |
|||
"description": "Available form field control types. Use canonical lowercase values for new descriptors and MCP mutations; compatibility aliases are accepted for older descriptors.", |
|||
"markdownDescription": "Use canonical lowercase values in MCP/write payloads: `text`, `textarea`, `number`, `checkbox`, `date`, `datetime`, `select`, `lookup`, `guid`, `computed`, `time`, `file`, `image`, or `money`. `text`/`textarea` are for strings, `number` for numeric values, `checkbox` for boolean, `date`/`datetime`/`time` for temporal values, `select` for enum values, `lookup` for foreign keys, `guid` for GUID input/display, `computed` for unbound calculated/display fields, `file` and `image` for upload fields, and `money` for money/decimal amount input. There is no `email` field type; use `text` plus validations where needed. Legacy PascalCase aliases remain accepted by the schema for compatibility.", |
|||
"type": "string", |
|||
"enum": [ |
|||
"text", |
|||
"Text", |
|||
"textarea", |
|||
"Textarea", |
|||
"number", |
|||
"Number", |
|||
"checkbox", |
|||
"Checkbox", |
|||
"date", |
|||
"Date", |
|||
"datetime", |
|||
"DateTime", |
|||
"select", |
|||
"Select", |
|||
"lookup", |
|||
"Lookup", |
|||
"guid", |
|||
"Guid", |
|||
"computed", |
|||
"Computed", |
|||
"time", |
|||
"Time", |
|||
"file", |
|||
"File", |
|||
"image", |
|||
"Image", |
|||
"money", |
|||
"Money" |
|||
], |
|||
"enumDescriptions": [ |
|||
"Single-line text input.", |
|||
"Single-line text input.", |
|||
"Multi-line text input.", |
|||
"Multi-line text input.", |
|||
"Numeric input.", |
|||
"Numeric input.", |
|||
"Boolean checkbox.", |
|||
"Boolean checkbox.", |
|||
"Date-only input.", |
|||
"Date-only input.", |
|||
"Date and time input.", |
|||
"Date and time input.", |
|||
"Enum/select input.", |
|||
"Enum/select input.", |
|||
"Foreign key lookup/autocomplete input.", |
|||
"Foreign key lookup/autocomplete input.", |
|||
"GUID input/display.", |
|||
"GUID input/display.", |
|||
"Unbound computed/display field.", |
|||
"Unbound computed/display field.", |
|||
"Time-only input.", |
|||
"Time-only input.", |
|||
"File upload field.", |
|||
"File upload field.", |
|||
"Image upload field.", |
|||
"Image upload field.", |
|||
"Money amount input.", |
|||
"Money amount input." |
|||
] |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "form-layout-descriptor.schema.json", |
|||
"title": "FormLayoutDescriptor", |
|||
"description": "Describes the visual layout of a form as tabs, groups, rows, and field cells.", |
|||
"markdownDescription": "AI guidance: the layout is a tree: tabs -> groups -> rows -> cells. Each cell `fieldId` must reference a field from the form's `fields` array. Use `colSpan` 4 for full-width fields, 2+2 for two columns, or 1+1+1+1 for four compact controls. The total effective width in a row should not exceed 4.", |
|||
"type": "object", |
|||
"properties": { |
|||
"tabs": { |
|||
"type": "array", |
|||
"description": "Ordered list of tabs in the form. Use one default tab named 'main' for simple forms.", |
|||
"minItems": 1, |
|||
"items": { |
|||
"type": "object", |
|||
"properties": { |
|||
"id": { |
|||
"type": "string", |
|||
"description": "Unique identifier for this tab. Prefer camelCase/kebab-case such as 'main' or 'advanced'.", |
|||
"minLength": 1 |
|||
}, |
|||
"title": { |
|||
"type": "string", |
|||
"description": "Display title for the tab", |
|||
"minLength": 1 |
|||
}, |
|||
"isDefault": { |
|||
"type": "boolean", |
|||
"description": "Whether this is the default tab. The designer uses the default tab as the safe target for orphaned fields.", |
|||
"default": false |
|||
}, |
|||
"groups": { |
|||
"type": "array", |
|||
"description": "Ordered list of groups within this tab. Use one default group for simple forms.", |
|||
"minItems": 1, |
|||
"items": { |
|||
"type": "object", |
|||
"properties": { |
|||
"id": { |
|||
"type": "string", |
|||
"description": "Unique identifier for this group. Rules can target group ids.", |
|||
"minLength": 1 |
|||
}, |
|||
"title": { |
|||
"type": ["string", "null"], |
|||
"description": "Optional display title for the group. Use null or omit for an untitled group." |
|||
}, |
|||
"isDefault": { |
|||
"type": "boolean", |
|||
"description": "Whether this is the default group. The designer uses the default group as the safe target for orphaned fields.", |
|||
"default": false |
|||
}, |
|||
"rows": { |
|||
"type": "array", |
|||
"description": "Ordered list of layout rows; each row contains one or more cells placed side-by-side.", |
|||
"items": { |
|||
"type": "object", |
|||
"properties": { |
|||
"cells": { |
|||
"type": "array", |
|||
"description": "Fields placed side-by-side in this row. The sum of colSpan values should not exceed 4.", |
|||
"minItems": 1, |
|||
"items": { |
|||
"type": "object", |
|||
"properties": { |
|||
"fieldId": { |
|||
"type": "string", |
|||
"description": "Reference to a field id in the form's fields array. Every field shown in the layout needs a matching field descriptor.", |
|||
"minLength": 1 |
|||
}, |
|||
"colSpan": { |
|||
"type": "integer", |
|||
"description": "Number of grid columns this field spans from 1 to 4. Use 4 for full-width fields.", |
|||
"minimum": 1, |
|||
"maximum": 4, |
|||
"default": 4 |
|||
}, |
|||
"colStart": { |
|||
"type": ["integer", "null"], |
|||
"description": "Starting grid column from 1 to 4. Omit or null to auto-place after the previous cell.", |
|||
"minimum": 1, |
|||
"maximum": 4 |
|||
} |
|||
}, |
|||
"required": ["fieldId"], |
|||
"additionalProperties": false |
|||
} |
|||
} |
|||
}, |
|||
"required": ["cells"], |
|||
"additionalProperties": false |
|||
} |
|||
} |
|||
}, |
|||
"required": ["id", "rows"], |
|||
"additionalProperties": false |
|||
} |
|||
} |
|||
}, |
|||
"required": ["id", "title", "groups"], |
|||
"additionalProperties": false |
|||
} |
|||
} |
|||
}, |
|||
"required": ["tabs"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"tabs": [ |
|||
{ |
|||
"id": "main", |
|||
"title": "Main", |
|||
"isDefault": true, |
|||
"groups": [ |
|||
{ |
|||
"id": "details", |
|||
"title": "Details", |
|||
"isDefault": true, |
|||
"rows": [ |
|||
{ "cells": [{ "fieldId": "title", "colSpan": 4 }] }, |
|||
{ "cells": [{ "fieldId": "startDate", "colSpan": 2 }, { "fieldId": "endDate", "colSpan": 2 }] } |
|||
] |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "form-rule-descriptor.schema.json", |
|||
"title": "FormRuleDescriptor", |
|||
"description": "Describes a conditional form rule with one or more actions that execute when the condition is met.", |
|||
"markdownDescription": "AI guidance: use rules for simple client-side behavior such as hiding a group when a checkbox is false, disabling a field after a status is selected, or setting a default value. `condition.fieldId` and every action `targetId` must reference existing field/group ids. Rules are not security boundaries; enforce sensitive behavior with backend validation/interceptors too.", |
|||
"type": "object", |
|||
"properties": { |
|||
"id": { |
|||
"type": "string", |
|||
"description": "Unique identifier for this rule. Prefer kebab-case or camelCase such as 'show-archive-reason'.", |
|||
"minLength": 1 |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Human-readable name for this rule, used by designers/documentation." |
|||
}, |
|||
"condition": { |
|||
"type": "object", |
|||
"description": "The condition that triggers this rule. For isEmpty/isNotEmpty, omit value.", |
|||
"properties": { |
|||
"fieldId": { |
|||
"type": "string", |
|||
"description": "The field whose value is evaluated. Must match a field id in the same form.", |
|||
"minLength": 1 |
|||
}, |
|||
"operator": { |
|||
"type": "string", |
|||
"enum": ["equals", "notEquals", "isEmpty", "isNotEmpty"], |
|||
"description": "Comparison operator. Use equals/notEquals with value; use isEmpty/isNotEmpty without value." |
|||
}, |
|||
"value": { |
|||
"description": "The value to compare against (not used for isEmpty/isNotEmpty)" |
|||
} |
|||
}, |
|||
"required": ["fieldId", "operator"], |
|||
"additionalProperties": false |
|||
}, |
|||
"actions": { |
|||
"type": "array", |
|||
"description": "Actions to perform when the condition is met. Actions are executed in order.", |
|||
"minItems": 1, |
|||
"items": { |
|||
"type": "object", |
|||
"properties": { |
|||
"type": { |
|||
"type": "string", |
|||
"enum": ["hide", "show", "disable", "enable", "setValue"], |
|||
"description": "The action type. hide/show/disable/enable target fields or groups; setValue targets fields." |
|||
}, |
|||
"targetType": { |
|||
"type": "string", |
|||
"enum": ["field", "group"], |
|||
"description": "Whether the target is a field or a group." |
|||
}, |
|||
"targetId": { |
|||
"type": "string", |
|||
"description": "The id of the target field or group. Must exist in this form's fields or layout groups.", |
|||
"minLength": 1 |
|||
}, |
|||
"value": { |
|||
"description": "The value to set. Used only for setValue actions." |
|||
} |
|||
}, |
|||
"required": ["type", "targetType", "targetId"], |
|||
"additionalProperties": false |
|||
} |
|||
} |
|||
}, |
|||
"required": ["id", "condition", "actions"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"id": "show-archive-reason", |
|||
"name": "Show archive reason", |
|||
"condition": { "fieldId": "status", "operator": "equals", "value": 3 }, |
|||
"actions": [ |
|||
{ "type": "show", "targetType": "field", "targetId": "archiveReason" } |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "interceptor-type.schema.json", |
|||
"title": "InterceptorType", |
|||
"description": "When an entity command interceptor runs. Prefer PascalCase values shown here.", |
|||
"markdownDescription": "`Pre` runs before the command is persisted and can block with `globalError`. `Post` runs after the command succeeds and is best for logging, notifications, publishing events, or enqueueing jobs. `Replace` is reserved for replacing command behavior where supported and should be avoided unless the host explicitly supports it.", |
|||
"type": "string", |
|||
"enum": [ |
|||
"Pre", |
|||
"pre", |
|||
"Post", |
|||
"post", |
|||
"Replace", |
|||
"replace" |
|||
], |
|||
"enumDescriptions": [ |
|||
"Before persistence; can block with globalError.", |
|||
"Before persistence; can block with globalError.", |
|||
"After successful persistence; use for side effects.", |
|||
"After successful persistence; use for side effects.", |
|||
"Replace command behavior where supported.", |
|||
"Replace command behavior where supported." |
|||
] |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "page-column-descriptor.schema.json", |
|||
"title": "PageColumnDescriptor", |
|||
"description": "Describes a property displayed by an entity page list, card, calendar event, or gallery item.", |
|||
"markdownDescription": "AI guidance: use page columns/card fields to control runtime page visibility. Each `propertyName` must match a property on the page entity. Keep this separate from entity property definitions so the same entity can have different views on different pages.", |
|||
"type": "object", |
|||
"properties": { |
|||
"propertyName": { |
|||
"type": "string", |
|||
"description": "Property displayed by this column/card field. Must match a property on the page entityName.", |
|||
"minLength": 1 |
|||
}, |
|||
"label": { |
|||
"type": "string", |
|||
"description": "Optional page-specific label override. Omit to use the property displayName/name.", |
|||
"minLength": 1 |
|||
}, |
|||
"order": { |
|||
"type": "integer", |
|||
"description": "Display order. Lower values appear first.", |
|||
"default": 0 |
|||
}, |
|||
"exportOrder": { |
|||
"type": "integer", |
|||
"description": "Optional export order. Lower values appear first in Excel, CSV, download-link columns, and file bundles. Omit to reuse display order." |
|||
}, |
|||
"width": { |
|||
"type": "string", |
|||
"description": "Optional CSS width for tabular columns, for example '160px', '12rem', or '20%'.", |
|||
"minLength": 1 |
|||
}, |
|||
"visible": { |
|||
"type": "boolean", |
|||
"description": "Whether the field is visible by default on this page.", |
|||
"default": true |
|||
}, |
|||
"exportable": { |
|||
"type": "boolean", |
|||
"description": "Whether the field can be exported from this page.", |
|||
"default": true |
|||
} |
|||
}, |
|||
"required": ["propertyName"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ "propertyName": "Title", "label": "Title", "order": 0, "exportOrder": 0, "width": "240px", "visible": true }, |
|||
{ "propertyName": "InternalNotes", "visible": false, "exportable": false } |
|||
] |
|||
} |
|||
@ -0,0 +1,260 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "page-descriptor.schema.json", |
|||
"title": "PageDescriptor", |
|||
"description": "Describes a runtime UI page. Pages create menu items and choose how an entity or dashboard is rendered.", |
|||
"markdownDescription": "AI guidance: `name` is the stable route/menu key and should be URL-safe kebab-case (for example `customers` or `event-calendar`). `title` is user-facing. For entity pages, set `entityName` to an existing entity. Configure list/gallery/card visibility with `columns`, filtering with `filters`, and create/edit UI with form names. For `dashboard` pages, provide `dashboard` and omit entity-only fields unless intentionally supported by the runtime. Do not put page-only settings on entity properties.", |
|||
"type": "object", |
|||
"properties": { |
|||
"$schema": { |
|||
"type": "string", |
|||
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file." |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Stable URL-safe page identifier. Prefer kebab-case, for example 'customers', 'event-calendar', or 'sales-dashboard'. This becomes part of the runtime route and is referenced by saveSuccessPageName.", |
|||
"minLength": 1 |
|||
}, |
|||
"title": { |
|||
"type": "string", |
|||
"description": "Display title for the menu item and page header.", |
|||
"minLength": 1 |
|||
}, |
|||
"icon": { |
|||
"type": "string", |
|||
"description": "FontAwesome icon class for the menu item, for example 'fa-solid fa-users'." |
|||
}, |
|||
"type": { |
|||
"$ref": "page-type.schema.json" |
|||
}, |
|||
"entityName": { |
|||
"type": "string", |
|||
"description": "Full name of the root entity this page displays. Required for dataGrid, kanban, calendar, gallery, and form pages. Usually omitted for dashboard pages.", |
|||
"minLength": 1 |
|||
}, |
|||
"groupByProperty": { |
|||
"type": "string", |
|||
"description": "Property name to group entities by. Required for kanban pages. Must reference a property on entityName, preferably an enum/status property with a small fixed set of values.", |
|||
"minLength": 1 |
|||
}, |
|||
"calendarStartProperty": { |
|||
"type": "string", |
|||
"description": "Date or DateTime property used as the start date for calendar pages. Required for calendar pages.", |
|||
"minLength": 1 |
|||
}, |
|||
"calendarEndProperty": { |
|||
"type": "string", |
|||
"description": "Optional Date or DateTime property used as the end date for calendar pages. Use when events can span a date range.", |
|||
"minLength": 1 |
|||
}, |
|||
"calendarTimeProperty": { |
|||
"type": "string", |
|||
"description": "Optional Time property used as the start time for calendar pages when the start date is date-only or time is stored separately.", |
|||
"minLength": 1 |
|||
}, |
|||
"calendarDurationProperty": { |
|||
"type": "string", |
|||
"description": "Optional numeric property used as duration in minutes for calendar pages. Use this instead of calendarEndProperty when records store duration.", |
|||
"minLength": 1 |
|||
}, |
|||
"galleryImageProperty": { |
|||
"type": "string", |
|||
"description": "Optional Image property used as the cover image for gallery pages. The property should have entity property type image.", |
|||
"minLength": 1 |
|||
}, |
|||
"defaultSortProperty": { |
|||
"type": "string", |
|||
"description": "Optional property used as the default sorting field when the client does not send explicit sorting. Must reference a property on entityName.", |
|||
"minLength": 1 |
|||
}, |
|||
"defaultSortDescending": { |
|||
"type": "boolean", |
|||
"description": "Whether the default sort property is sorted descending.", |
|||
"default": false |
|||
}, |
|||
"defaultFileExportMode": { |
|||
"type": "integer", |
|||
"enum": [0, 1, 2], |
|||
"description": "Default file export mode for file/image columns. 0 exports file names, 1 exports metadata columns, and 2 exports download-link columns." |
|||
}, |
|||
"allowFileBundleExport": { |
|||
"type": "boolean", |
|||
"description": "Whether file bundle export is available for this page.", |
|||
"default": true |
|||
}, |
|||
"columns": { |
|||
"type": "array", |
|||
"description": "Page-owned column/card field configuration. Use for dataGrid, kanban card fields, calendar event fields, and gallery card fields. Each propertyName must reference a property on entityName.", |
|||
"items": { |
|||
"$ref": "page-column-descriptor.schema.json" |
|||
} |
|||
}, |
|||
"filters": { |
|||
"type": "array", |
|||
"description": "Page-owned filter configuration for filterable page types. Each propertyName must reference a property on entityName. Use this instead of legacy property-level UI filter settings.", |
|||
"items": { |
|||
"$ref": "page-filter-descriptor.schema.json" |
|||
} |
|||
}, |
|||
"order": { |
|||
"type": "integer", |
|||
"description": "Menu sort order (lower values appear first)", |
|||
"default": 0 |
|||
}, |
|||
"formName": { |
|||
"type": "string", |
|||
"description": "Name of the form to render. Required when type is 'form'. Must match a form descriptor whose entityName matches this page entityName.", |
|||
"minLength": 1 |
|||
}, |
|||
"createFormName": { |
|||
"type": "string", |
|||
"description": "Name of the form to use when creating records from dataGrid, kanban, calendar, or gallery pages. Must match a form descriptor for the same entity." |
|||
}, |
|||
"editFormName": { |
|||
"type": "string", |
|||
"description": "Name of the form to use when editing records from dataGrid, kanban, calendar, or gallery pages. Must match a form descriptor for the same entity." |
|||
}, |
|||
"createFormDisplay": { |
|||
"type": "string", |
|||
"enum": ["modal", "Modal", "page", "Page"], |
|||
"description": "How to display the create form. Use 'modal' for quick CRUD; use 'page' for longer forms or when deep links are desired.", |
|||
"default": "modal" |
|||
}, |
|||
"editFormDisplay": { |
|||
"type": "string", |
|||
"enum": ["modal", "Modal", "page", "Page"], |
|||
"description": "How to display the edit form. Use 'modal' for quick CRUD; use 'page' for longer forms or when deep links are desired.", |
|||
"default": "modal" |
|||
}, |
|||
"saveSuccessNavigation": { |
|||
"type": "string", |
|||
"enum": ["stay", "page", "url"], |
|||
"description": "Navigation behavior after a standalone form page saves successfully. 'stay' keeps the user on the form page; 'page' navigates to saveSuccessPageName; 'url' navigates to saveSuccessUrl.", |
|||
"default": "stay" |
|||
}, |
|||
"saveSuccessPageName": { |
|||
"type": "string", |
|||
"description": "Existing page name to open after a form page saves successfully when saveSuccessNavigation is 'page'.", |
|||
"minLength": 1 |
|||
}, |
|||
"saveSuccessUrl": { |
|||
"type": "string", |
|||
"description": "URL or application path to open after a form page saves successfully when saveSuccessNavigation is 'url'.", |
|||
"minLength": 1 |
|||
}, |
|||
"dashboard": { |
|||
"$ref": "dashboard-descriptor.schema.json", |
|||
"description": "Dashboard layout and visualizations. Required only when type is dashboard." |
|||
}, |
|||
"group": { |
|||
"type": "string", |
|||
"description": "Name of the page group this page belongs to. Must match pageGroups[].name. Omit for root-level pages.", |
|||
"minLength": 1 |
|||
}, |
|||
"permissionConfig": { |
|||
"$ref": "page-permission-config.schema.json", |
|||
"description": "Optional page operation permission overrides. Omit operations to use generated defaults." |
|||
} |
|||
}, |
|||
"required": ["name", "title", "type"], |
|||
"allOf": [ |
|||
{ |
|||
"if": { |
|||
"properties": { "type": { "enum": ["dataGrid", "DataGrid"] } } |
|||
}, |
|||
"then": { |
|||
"required": ["entityName"] |
|||
} |
|||
}, |
|||
{ |
|||
"if": { |
|||
"properties": { "type": { "enum": ["kanban", "Kanban"] } } |
|||
}, |
|||
"then": { |
|||
"required": ["entityName", "groupByProperty"] |
|||
} |
|||
}, |
|||
{ |
|||
"if": { |
|||
"properties": { "type": { "enum": ["calendar", "Calendar"] } } |
|||
}, |
|||
"then": { |
|||
"required": ["entityName", "calendarStartProperty"] |
|||
} |
|||
}, |
|||
{ |
|||
"if": { |
|||
"properties": { "type": { "enum": ["gallery", "Gallery"] } } |
|||
}, |
|||
"then": { |
|||
"required": ["entityName"] |
|||
} |
|||
}, |
|||
{ |
|||
"if": { |
|||
"properties": { "type": { "enum": ["form", "Form"] } } |
|||
}, |
|||
"then": { |
|||
"required": ["entityName", "formName"] |
|||
} |
|||
}, |
|||
{ |
|||
"if": { |
|||
"properties": { "type": { "enum": ["dashboard", "Dashboard"] } } |
|||
}, |
|||
"then": { |
|||
"required": ["dashboard"] |
|||
} |
|||
} |
|||
], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "events", |
|||
"title": "Events", |
|||
"type": "dataGrid", |
|||
"entityName": "Acme.Events.Event", |
|||
"columns": [ |
|||
{ "propertyName": "Title", "order": 0 }, |
|||
{ "propertyName": "Status", "order": 1 } |
|||
], |
|||
"filters": [ |
|||
{ "propertyName": "Title", "control": "text", "defaultOperator": "contains" } |
|||
], |
|||
"createFormName": "event-form", |
|||
"editFormName": "event-form" |
|||
}, |
|||
{ |
|||
"name": "event-calendar", |
|||
"title": "Event Calendar", |
|||
"type": "calendar", |
|||
"entityName": "Acme.Events.Event", |
|||
"calendarStartProperty": "StartDate", |
|||
"calendarEndProperty": "EndDate" |
|||
}, |
|||
{ |
|||
"name": "event-dashboard", |
|||
"title": "Event Dashboard", |
|||
"type": "dashboard", |
|||
"dashboard": { |
|||
"rows": [ |
|||
{ |
|||
"items": [ |
|||
{ |
|||
"name": "events-by-status", |
|||
"type": "chart", |
|||
"title": "Events by Status", |
|||
"entityName": "Acme.Events.Event", |
|||
"chart": { |
|||
"chartType": "bar", |
|||
"xAxis": { "property": "Status" }, |
|||
"yAxis": [{ "aggregation": "count", "label": "Events" }] |
|||
} |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "page-filter-descriptor.schema.json", |
|||
"title": "PageFilterDescriptor", |
|||
"description": "Describes a runtime filter control for an entity page.", |
|||
"markdownDescription": "AI guidance: each filter must point to a page entity property. Use `control: \"auto\"` unless a specific control is needed. Use text/contains for string search, range/between for numeric values, dateRange/timeRange for temporal values, select/multiSelect for enums, lookup for foreign keys, and exists for null checks. Do not use the legacy `operator` property; use `defaultOperator`.", |
|||
"type": "object", |
|||
"properties": { |
|||
"propertyName": { |
|||
"type": "string", |
|||
"description": "Property filtered by this control. Must match a property on the page entityName.", |
|||
"minLength": 1 |
|||
}, |
|||
"label": { |
|||
"type": "string", |
|||
"description": "Optional page-specific label override. Omit to use the property displayName/name.", |
|||
"minLength": 1 |
|||
}, |
|||
"order": { |
|||
"type": "integer", |
|||
"description": "Filter display order. Lower values appear first.", |
|||
"default": 0 |
|||
}, |
|||
"visible": { |
|||
"type": "boolean", |
|||
"description": "Whether the filter is shown at runtime", |
|||
"default": true |
|||
}, |
|||
"control": { |
|||
"type": "string", |
|||
"description": "Runtime control used by the filter. 'auto' selects based on property type; choose explicit controls for predictable generated UI.", |
|||
"enum": ["auto", "text", "range", "dateRange", "timeRange", "select", "multiSelect", "lookup", "exists"], |
|||
"default": "auto" |
|||
}, |
|||
"defaultOperator": { |
|||
"type": "string", |
|||
"description": "Default operator applied by this filter. Use 'contains' for text search, 'equal' for enum/FK exact matches, 'between' for ranges, and 'default' to let runtime choose.", |
|||
"enum": [ |
|||
"default", |
|||
"equal", |
|||
"notEqual", |
|||
"contains", |
|||
"notContains", |
|||
"startsWith", |
|||
"endsWith", |
|||
"greaterThan", |
|||
"greaterThanOrEqual", |
|||
"lessThan", |
|||
"lessThanOrEqual", |
|||
"between", |
|||
"hasValue", |
|||
"isNull", |
|||
"isNotNull", |
|||
"in", |
|||
"notIn" |
|||
] |
|||
}, |
|||
"allowedOperators": { |
|||
"type": "array", |
|||
"description": "Operators the runtime filter UI allows users to choose. Omit to let the runtime choose based on property type/control.", |
|||
"items": { |
|||
"type": "string", |
|||
"enum": [ |
|||
"default", |
|||
"equal", |
|||
"notEqual", |
|||
"contains", |
|||
"notContains", |
|||
"startsWith", |
|||
"endsWith", |
|||
"greaterThan", |
|||
"greaterThanOrEqual", |
|||
"lessThan", |
|||
"lessThanOrEqual", |
|||
"between", |
|||
"hasValue", |
|||
"isNull", |
|||
"isNotNull", |
|||
"in", |
|||
"notIn" |
|||
] |
|||
}, |
|||
"uniqueItems": true |
|||
}, |
|||
"defaultValue": { |
|||
"description": "Optional default filter value applied when the page first loads. Shape depends on control/operator." |
|||
}, |
|||
"placeholder": { |
|||
"type": "string", |
|||
"description": "Optional runtime input placeholder" |
|||
}, |
|||
"helpText": { |
|||
"type": "string", |
|||
"description": "Optional runtime help text" |
|||
}, |
|||
"allowMultipleValues": { |
|||
"type": "boolean", |
|||
"description": "Whether the filter can submit multiple values. Useful with multiSelect/in/notIn.", |
|||
"default": false |
|||
}, |
|||
"clearable": { |
|||
"type": "boolean", |
|||
"description": "Whether runtime users can clear the filter.", |
|||
"default": true |
|||
}, |
|||
"initiallyExpanded": { |
|||
"type": "boolean", |
|||
"description": "Whether the runtime filter panel opens expanded.", |
|||
"default": false |
|||
} |
|||
}, |
|||
"required": ["propertyName"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ "propertyName": "Title", "control": "text", "defaultOperator": "contains", "placeholder": "Search title" }, |
|||
{ "propertyName": "Status", "control": "select", "defaultOperator": "equal", "clearable": true }, |
|||
{ "propertyName": "StartDate", "control": "dateRange", "defaultOperator": "between" } |
|||
] |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "page-group-descriptor.schema.json", |
|||
"title": "PageGroupDescriptor", |
|||
"description": "Describes a menu group/folder that can contain pages and sub-groups.", |
|||
"markdownDescription": "AI guidance: create page groups when several pages belong to the same feature area. Pages reference groups by `group`. Nested groups reference parent groups by `parent`. Use stable kebab-case names and concise user-facing titles.", |
|||
"type": "object", |
|||
"properties": { |
|||
"$schema": { |
|||
"type": "string", |
|||
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file." |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Unique URL-safe identifier for the group. Prefer kebab-case, for example 'crm' or 'content-studio'.", |
|||
"minLength": 1 |
|||
}, |
|||
"title": { |
|||
"type": "string", |
|||
"description": "Display title for the menu group.", |
|||
"minLength": 1 |
|||
}, |
|||
"icon": { |
|||
"type": "string", |
|||
"description": "FontAwesome icon class, for example 'fa-solid fa-folder' or 'fa-solid fa-calendar-days'." |
|||
}, |
|||
"order": { |
|||
"type": "integer", |
|||
"description": "Sort order within the parent level (lower values appear first)", |
|||
"default": 0 |
|||
}, |
|||
"parent": { |
|||
"type": "string", |
|||
"description": "Name of the parent group for nesting. Must match another pageGroups[].name. Omit for root-level groups.", |
|||
"minLength": 1 |
|||
} |
|||
}, |
|||
"required": ["name", "title"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ "name": "events", "title": "Events", "icon": "fa-solid fa-calendar-days", "order": 10 }, |
|||
{ "name": "event-admin", "title": "Admin", "parent": "events", "order": 20 } |
|||
] |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "page-permission-config.schema.json", |
|||
"title": "PagePermissionConfig", |
|||
"description": "Permission configuration for a page's operations. Only overridden values are stored; omitted values use auto-generated defaults based on the page.", |
|||
"markdownDescription": "AI guidance: each operation can be `default`, `public`, `authenticated`, or a permission name. Omit an operation to use runtime-generated defaults. Use `public` only for anonymous pages. Use `authenticated` for pages any logged-in user can view. Use explicit permission names for controlled business operations.", |
|||
"type": "object", |
|||
"properties": { |
|||
"view": { |
|||
"type": "string", |
|||
"description": "Permission for viewing the page. Can be a specific permission name, 'default', 'public', or 'authenticated'. Omit to use auto-generated default." |
|||
}, |
|||
"create": { |
|||
"type": "string", |
|||
"description": "Permission for create operation on this page. Can be a specific permission name, 'default', 'public', or 'authenticated'. Omit to use auto-generated default." |
|||
}, |
|||
"update": { |
|||
"type": "string", |
|||
"description": "Permission for update operation on this page. Can be a specific permission name, 'default', 'public', or 'authenticated'. Omit to use auto-generated default." |
|||
}, |
|||
"delete": { |
|||
"type": "string", |
|||
"description": "Permission for delete operation on this page. Can be a specific permission name, 'default', 'public', or 'authenticated'. Omit to use auto-generated default." |
|||
} |
|||
}, |
|||
"additionalProperties": false |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "page-type.schema.json", |
|||
"title": "PageType", |
|||
"description": "The runtime page renderer to use. Prefer lower-case values in descriptor JSON; PascalCase aliases are accepted for compatibility.", |
|||
"markdownDescription": "`dataGrid` renders searchable/filterable tabular CRUD for an entity. `kanban` renders grouped cards and requires `groupByProperty`. `calendar` renders entity records on a calendar and requires `calendarStartProperty`. `gallery` renders visual cards and may use `galleryImageProperty`. `form` renders a standalone create/edit form page and requires `formName`. `dashboard` renders dashboard rows/visualizations and requires `dashboard`.", |
|||
"type": "string", |
|||
"enum": [ |
|||
"dataGrid", |
|||
"DataGrid", |
|||
"kanban", |
|||
"Kanban", |
|||
"calendar", |
|||
"Calendar", |
|||
"form", |
|||
"Form", |
|||
"dashboard", |
|||
"Dashboard", |
|||
"gallery", |
|||
"Gallery" |
|||
], |
|||
"enumDescriptions": [ |
|||
"Entity data grid/list page.", |
|||
"Entity data grid/list page.", |
|||
"Entity kanban board grouped by an enum/status property.", |
|||
"Entity kanban board grouped by an enum/status property.", |
|||
"Entity calendar page using date/time properties.", |
|||
"Entity calendar page using date/time properties.", |
|||
"Standalone form page bound to a form descriptor.", |
|||
"Standalone form page bound to a form descriptor.", |
|||
"Dashboard page with chart/list/number visualizations.", |
|||
"Dashboard page with chart/list/number visualizations.", |
|||
"Entity gallery/card page, optionally image-backed.", |
|||
"Entity gallery/card page, optionally image-backed." |
|||
] |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "permission-descriptor.schema.json", |
|||
"title": "PermissionDescriptor", |
|||
"description": "Defines a custom permission created through the designer. Supports parent-child hierarchy.", |
|||
"markdownDescription": "AI guidance: use stable dot-separated permission names, for example `Acme.Events.Create`. Define a parent permission for the feature and child permissions for operations. Pages and endpoints can reference these names. Permission definitions only declare permissions; roles/users must still be granted permissions through permission management.", |
|||
"type": "object", |
|||
"properties": { |
|||
"$schema": { |
|||
"type": "string", |
|||
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file." |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Unique dot-separated permission name, for example 'Acme.Events.Create'.", |
|||
"minLength": 1 |
|||
}, |
|||
"displayName": { |
|||
"type": "string", |
|||
"description": "Human-readable display name shown in permission management UI.", |
|||
"minLength": 1 |
|||
}, |
|||
"children": { |
|||
"type": "array", |
|||
"description": "Child permissions forming a hierarchy. Use for feature -> operation grouping.", |
|||
"items": { |
|||
"$ref": "#" |
|||
} |
|||
} |
|||
}, |
|||
"required": ["name", "displayName"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "Acme.Events", |
|||
"displayName": "Events", |
|||
"children": [ |
|||
{ "name": "Acme.Events.Create", "displayName": "Create events" }, |
|||
{ "name": "Acme.Events.Update", "displayName": "Update events" }, |
|||
{ "name": "Acme.Events.Delete", "displayName": "Delete events" } |
|||
] |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "script-background-job-descriptor.schema.json", |
|||
"title": "Script Background Job Descriptor", |
|||
"description": "Defines a named JavaScript background job handler.", |
|||
"markdownDescription": "AI guidance: use background jobs for asynchronous work that is explicitly enqueued by code/scripts, such as notifications, imports, or long-running recalculations. `name` is the job identifier used by enqueue calls. Keep scripts retry-safe and idempotent because background jobs may run more than once after failures.", |
|||
"type": "object", |
|||
"properties": { |
|||
"$schema": { |
|||
"type": "string", |
|||
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file." |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Unique identifier for the background job. Scripts/enqueuers reference this name.", |
|||
"minLength": 1 |
|||
}, |
|||
"javascript": { |
|||
"type": "string", |
|||
"description": "JavaScript code to execute when the job is enqueued/run. Use context payload/job arguments where available and keep retry-safe.", |
|||
"minLength": 1 |
|||
}, |
|||
"description": { |
|||
"type": "string", |
|||
"description": "Optional description for designer documentation and model health context." |
|||
} |
|||
}, |
|||
"required": ["name", "javascript"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "SendEventReminder", |
|||
"javascript": "context.log('Sending event reminder job.');" |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "script-background-worker-descriptor.schema.json", |
|||
"title": "Script Background Worker Descriptor", |
|||
"description": "Defines a scheduled JavaScript background worker.", |
|||
"markdownDescription": "AI guidance: use background workers for recurring scheduled work. Provide either `period` in milliseconds or `cronExpression`; do not provide both unless the host explicitly chooses one. Keep scripts idempotent and short. Use workers for polling, cleanup, synchronization, or recurring summary generation.", |
|||
"type": "object", |
|||
"properties": { |
|||
"$schema": { |
|||
"type": "string", |
|||
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file." |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Unique identifier for the background worker.", |
|||
"minLength": 1 |
|||
}, |
|||
"period": { |
|||
"type": "integer", |
|||
"description": "Execution period in milliseconds. Example: 300000 for every 5 minutes.", |
|||
"minimum": 1 |
|||
}, |
|||
"cronExpression": { |
|||
"type": ["string", "null"], |
|||
"description": "Cron expression for scheduler-backed providers. Omit it, set it to null, or leave it empty when the worker uses period-based scheduling." |
|||
}, |
|||
"javascript": { |
|||
"type": "string", |
|||
"description": "JavaScript code to execute when the worker runs. Keep idempotent; workers may overlap or retry depending on scheduler configuration.", |
|||
"minLength": 1 |
|||
}, |
|||
"description": { |
|||
"type": "string", |
|||
"description": "Optional description for designer documentation and model health context." |
|||
} |
|||
}, |
|||
"required": ["name", "javascript"], |
|||
"anyOf": [ |
|||
{ |
|||
"required": ["period"], |
|||
"not": { |
|||
"required": ["cronExpression"], |
|||
"properties": { |
|||
"cronExpression": { |
|||
"type": "string", |
|||
"minLength": 1 |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"required": ["cronExpression"], |
|||
"not": { "required": ["period"] }, |
|||
"properties": { |
|||
"cronExpression": { |
|||
"type": "string", |
|||
"minLength": 1 |
|||
} |
|||
} |
|||
} |
|||
], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "CleanupExpiredEvents", |
|||
"period": 3600000, |
|||
"javascript": "context.log('Running expired event cleanup.');" |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "script-event-handler-descriptor.schema.json", |
|||
"title": "Script Event Handler Descriptor", |
|||
"description": "Defines a named JavaScript handler for a distributed event name.", |
|||
"markdownDescription": "AI guidance: use event handlers for asynchronous reactions to domain/application events. `eventName` must match the event name published by the application or another script. Keep handlers idempotent because distributed events can be retried. Scripts can use the host-provided context, including event payload/request data where available, db, current tenant/user, email, logging, event publishing, and job enqueue helpers.", |
|||
"type": "object", |
|||
"properties": { |
|||
"$schema": { |
|||
"type": "string", |
|||
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file." |
|||
}, |
|||
"name": { |
|||
"type": "string", |
|||
"description": "Unique identifier for the handler. Prefer a descriptive name such as 'NotifyWhenEventCompleted'.", |
|||
"minLength": 1 |
|||
}, |
|||
"eventName": { |
|||
"type": "string", |
|||
"description": "Distributed event name to subscribe to. Must exactly match the publisher's event name.", |
|||
"minLength": 1 |
|||
}, |
|||
"javascript": { |
|||
"type": "string", |
|||
"description": "JavaScript code to execute when the event is received. Keep idempotent; avoid assuming one-time delivery.", |
|||
"minLength": 1 |
|||
}, |
|||
"description": { |
|||
"type": "string", |
|||
"description": "Optional description for designer documentation and model health context." |
|||
} |
|||
}, |
|||
"required": ["name", "eventName", "javascript"], |
|||
"additionalProperties": false, |
|||
"examples": [ |
|||
{ |
|||
"name": "NotifyEventCompleted", |
|||
"eventName": "Acme.Events.EventCompleted", |
|||
"javascript": "context.log('Event completed handler executed.');" |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"$id": "validator-descriptor.schema.json", |
|||
"title": "ValidatorDescriptor", |
|||
"description": "A single backend/UI validator applied to an entity property or form field.", |
|||
"markdownDescription": "AI guidance: always set `type`. Use `required` for mandatory input, `minLength`/`maxLength`/`stringLength` for strings, `min`/`max`/`range` for numbers and comparable values, `pattern` or `regularExpression` for regex, and `email`, `phone`, `url`, or `creditCard` for common formats. Use lower-case validator names for new JSON. Add `message` only when a custom localized/user-facing message is needed.", |
|||
"type": "object", |
|||
"required": ["type"], |
|||
"properties": { |
|||
"type": { |
|||
"type": "string", |
|||
"description": "Validator type. Prefer lower-case values in descriptor JSON; PascalCase aliases are accepted for compatibility.", |
|||
"enum": [ |
|||
"required", |
|||
"Required", |
|||
"minLength", |
|||
"MinLength", |
|||
"maxLength", |
|||
"MaxLength", |
|||
"stringLength", |
|||
"StringLength", |
|||
"min", |
|||
"Min", |
|||
"minimum", |
|||
"Minimum", |
|||
"max", |
|||
"Max", |
|||
"maximum", |
|||
"Maximum", |
|||
"range", |
|||
"Range", |
|||
"pattern", |
|||
"Pattern", |
|||
"regularExpression", |
|||
"RegularExpression", |
|||
"email", |
|||
"Email", |
|||
"emailAddress", |
|||
"EmailAddress", |
|||
"phone", |
|||
"Phone", |
|||
"url", |
|||
"Url", |
|||
"creditCard", |
|||
"CreditCard" |
|||
] |
|||
}, |
|||
"message": { |
|||
"type": "string", |
|||
"description": "Optional custom error message shown when validation fails. Omit to use the default localized message." |
|||
}, |
|||
"length": { |
|||
"type": "integer", |
|||
"description": "Length value for minLength or maxLength validators. Example: { \"type\": \"maxLength\", \"length\": 128 }.", |
|||
"minimum": 0 |
|||
}, |
|||
"minimumLength": { |
|||
"type": "integer", |
|||
"description": "Minimum length for stringLength validator.", |
|||
"minimum": 0 |
|||
}, |
|||
"maximumLength": { |
|||
"type": "integer", |
|||
"description": "Maximum length for stringLength validator.", |
|||
"minimum": 0 |
|||
}, |
|||
"value": { |
|||
"type": "number", |
|||
"description": "Generic numeric value alias for single-value validators such as min/minimum, max/maximum, minLength/maxLength, and stringLength maximumLength." |
|||
}, |
|||
"minimum": { |
|||
"type": "number", |
|||
"description": "Minimum value for min/minimum or range validators." |
|||
}, |
|||
"maximum": { |
|||
"type": "number", |
|||
"description": "Maximum value for max/maximum or range validators." |
|||
}, |
|||
"pattern": { |
|||
"type": "string", |
|||
"description": "Regular expression pattern for pattern/regularExpression validators. Store the regex pattern string only; do not include leading/trailing slashes." |
|||
}, |
|||
"allowEmptyStrings": { |
|||
"type": "boolean", |
|||
"description": "Whether required validation should allow empty strings. Usually false for user-entered text." |
|||
} |
|||
}, |
|||
"additionalProperties": true, |
|||
"examples": [ |
|||
{ "type": "required" }, |
|||
{ "type": "maxLength", "length": 128 }, |
|||
{ "type": "range", "minimum": 0, "maximum": 100 }, |
|||
{ "type": "pattern", "pattern": "^[A-Z]{3}-[0-9]{4}$", "message": "Code must match ABC-1234." }, |
|||
{ "type": "email" } |
|||
] |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
{ |
|||
"$schema": "https://json-schema.org/draft/2020-12/schema", |
|||
"name": "ABP Low-Code Model Descriptor Schemas", |
|||
"version": "10.5", |
|||
"publicBaseUrl": "https://raw.githubusercontent.com/abpframework/abp/rel-10.5/schemas/low-code", |
|||
"definitionsPath": "definitions", |
|||
"descriptorSchemas": { |
|||
"enums": "definitions/enum-descriptor.schema.json", |
|||
"entities": "definitions/entity-descriptor.schema.json", |
|||
"endpoints": "definitions/endpoint-descriptor.schema.json", |
|||
"eventHandlers": "definitions/script-event-handler-descriptor.schema.json", |
|||
"backgroundJobs": "definitions/script-background-job-descriptor.schema.json", |
|||
"backgroundWorkers": "definitions/script-background-worker-descriptor.schema.json", |
|||
"pageGroups": "definitions/page-group-descriptor.schema.json", |
|||
"pages": "definitions/page-descriptor.schema.json", |
|||
"forms": "definitions/form-descriptor.schema.json", |
|||
"permissions": "definitions/permission-descriptor.schema.json" |
|||
} |
|||
} |
|||
Loading…
Reference in new issue