@dforge-core/dforge-mcp 0.1.0-rc.9 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/README.md +88 -29
  3. package/dist/server.js +2735 -351
  4. package/docs/creating-modules.md +15 -7
  5. package/package.json +11 -6
  6. package/resources/docs/conventions.md +22 -16
  7. package/resources/docs/dsl-reference.md +767 -0
  8. package/resources/schemas/entity.schema.json +8 -1
  9. package/resources/schemas/print-templates.schema.json +82 -0
  10. package/resources/schemas/reports.schema.json +4 -0
  11. package/resources/schemas/triggers.schema.json +59 -0
  12. package/skills/dforge-mcp-author/SKILL.md +284 -73
  13. package/skills/dforge-mcp-author/examples/matrix-budget/README.md +43 -0
  14. package/skills/dforge-mcp-author/examples/matrix-budget/entities/budget_category.json +24 -0
  15. package/skills/dforge-mcp-author/examples/matrix-budget/entities/budget_line.json +56 -0
  16. package/skills/dforge-mcp-author/examples/matrix-budget/manifest.json +30 -0
  17. package/skills/dforge-mcp-author/examples/matrix-budget/security/roles.json +9 -0
  18. package/skills/dforge-mcp-author/examples/matrix-budget/seed-data/01-categories.json +8 -0
  19. package/skills/dforge-mcp-author/examples/matrix-budget/ui/data_views.json +42 -0
  20. package/skills/dforge-mcp-author/examples/simple-todo/README.md +38 -0
  21. package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_item.json +83 -0
  22. package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_list.json +43 -0
  23. package/skills/dforge-mcp-author/examples/simple-todo/logic/actions/mark_done.dsl +6 -0
  24. package/skills/dforge-mcp-author/examples/simple-todo/manifest.json +32 -0
  25. package/skills/dforge-mcp-author/examples/simple-todo/security/roles.json +10 -0
  26. package/skills/dforge-mcp-author/examples/simple-todo/seed-data/01-lists.json +17 -0
  27. package/skills/dforge-mcp-author/examples/simple-todo/ui/actions.json +11 -0
  28. package/skills/dforge-mcp-author/examples/simple-todo/ui/data_views.json +35 -0
  29. package/skills/dforge-mcp-author/examples/simple-todo/ui/menus.json +28 -0
  30. package/skills/dforge-mcp-author/references/action-dsl.md +397 -0
  31. package/skills/dforge-mcp-author/references/column-types.md +168 -0
  32. package/skills/dforge-mcp-author/references/conventions.md +177 -0
  33. package/skills/dforge-mcp-author/references/data-migration.md +270 -0
  34. package/skills/dforge-mcp-author/references/data-views.md +245 -0
  35. package/skills/dforge-mcp-author/references/excel-import.md +61 -0
  36. package/skills/dforge-mcp-author/references/field-types.md +144 -0
  37. package/skills/dforge-mcp-author/references/filters.md +326 -0
  38. package/skills/dforge-mcp-author/references/flags.md +73 -0
  39. package/skills/dforge-mcp-author/references/formulas.md +206 -0
  40. package/skills/dforge-mcp-author/references/jobs.md +149 -0
  41. package/skills/dforge-mcp-author/references/manifest.md +123 -0
  42. package/skills/dforge-mcp-author/references/menus.md +164 -0
  43. package/skills/dforge-mcp-author/references/number-sequences.md +117 -0
  44. package/skills/dforge-mcp-author/references/print-templates.md +159 -0
  45. package/skills/dforge-mcp-author/references/queries.md +312 -0
  46. package/skills/dforge-mcp-author/references/reports.md +398 -0
  47. package/skills/dforge-mcp-author/references/schema-import.md +331 -0
  48. package/skills/dforge-mcp-author/references/security.md +244 -0
  49. package/skills/dforge-mcp-author/references/settings.md +120 -0
  50. package/skills/dforge-mcp-author/references/traits.md +153 -0
  51. package/skills/dforge-mcp-author/references/translations.md +158 -0
  52. package/skills/dforge-mcp-author/references/validation-checklist.md +184 -0
  53. package/skills/dforge-mcp-author/scripts/xlsx_to_model.py +198 -0
@@ -0,0 +1,245 @@
1
+ # Data Views Reference
2
+
3
+ Data views define how users see and interact with entity data. One entity can have many data views (grid, kanban, gallery, tree-grid, list, calendar, matrix, card).
4
+
5
+ Lives in: `ui/data_views.json`
6
+
7
+ ## Structure
8
+
9
+ ```json
10
+ {
11
+ "contact_list": {
12
+ "label": "All Contacts",
13
+ "description": "Primary list of contacts",
14
+ "viewType": "grid",
15
+ "icon": "bi-people",
16
+ "dataSources": [{
17
+ "entityCode": "contact",
18
+ "label": "Contacts",
19
+ "columns": [
20
+ { "column_cd": "first_name", "width": 150 },
21
+ { "column_cd": "last_name", "width": 150 },
22
+ { "column_cd": "email", "width": 220 },
23
+ { "column_cd": "phone", "width": 130 },
24
+ { "column_cd": "account", "width": 200 }
25
+ ],
26
+ "filter": null
27
+ }],
28
+ "order": ["last_name"]
29
+ }
30
+ }
31
+ ```
32
+
33
+ ## Critical rule — `dataSources` array
34
+
35
+ **Always** put `entityCode`, `columns`, and per-source `filter` inside a `dataSources` array. Never at the root. The view-level `order` and `filter` sit at the view-def root, alongside `viewType`.
36
+
37
+ ```json
38
+ // WRONG — will fail validation
39
+ {
40
+ "contact_list": {
41
+ "label": "Contacts",
42
+ "viewType": "grid",
43
+ "entityCode": "contact", // WRONG — should be inside dataSources
44
+ "columns": [ /* ... */ ] // WRONG — should be inside dataSources
45
+ }
46
+ }
47
+
48
+ // RIGHT
49
+ {
50
+ "contact_list": {
51
+ "label": "Contacts",
52
+ "viewType": "grid",
53
+ "dataSources": [{
54
+ "entityCode": "contact",
55
+ "columns": [ /* ... */ ]
56
+ }]
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## View types
62
+
63
+ | `viewType` | Description | `viewConfig` | When to use |
64
+ |---|---|---|---|
65
+ | `grid` | Spreadsheet-style table with sortable columns | — | Default for most entities; bulk data |
66
+ | `list` | Compact list view | — | Mobile-friendly, card-sized items |
67
+ | `kanban` | Columns grouped by a status field | `groupByField` | Workflow entities with stages (deals, tickets) |
68
+ | `gallery` | Image-forward card grid | — | Image-heavy entities (products, real estate) |
69
+ | `tree-grid` | Hierarchical grid with expand/collapse | — | Tree structures (folders, categories, org charts) |
70
+ | `calendar` | Calendar display of date-based records | `dateField`, `titleField` | Schedules, due dates, events |
71
+ | `matrix` | Pivot grid: `rowAxis` × `colAxis`, one value-source record per cell | `rowAxis`, `colAxis`, `cell` | Cross-tab data: P&L (accounts × periods), timesheets (tasks × days), budgets (categories × months) |
72
+ | `card` | Single-record detail view | — | Record detail pages (implicit — every entity has one) |
73
+
74
+ ## View config (`viewConfig`)
75
+
76
+ Some view types need additional configuration via the `viewConfig` property:
77
+
78
+ ### Kanban
79
+
80
+ ```json
81
+ {
82
+ "opportunities_kanban": {
83
+ "viewType": "kanban",
84
+ "label": "Sales Pipeline",
85
+ "dataSources": [{
86
+ "entityCode": "opportunity",
87
+ "columns": ["opportunity_name", "account", "total_amount", "owner_id"]
88
+ }],
89
+ "viewConfig": {
90
+ "groupByField": "stage"
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ `groupByField` — the dropdown/status column to group cards by. Each distinct value becomes a kanban column.
97
+
98
+ ### Calendar
99
+
100
+ ```json
101
+ {
102
+ "invoices_calendar": {
103
+ "viewType": "calendar",
104
+ "label": "Invoice Calendar",
105
+ "dataSources": [{
106
+ "entityCode": "invoice",
107
+ "columns": ["invoice_number", "customer", "total_amount", "due_date"]
108
+ }],
109
+ "viewConfig": {
110
+ "dateField": "due_date",
111
+ "titleField": "invoice_number"
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ - `dateField` — the date column that determines where records appear on the calendar
118
+ - `titleField` — the column whose value is shown as the calendar event title
119
+
120
+ ### Matrix
121
+
122
+ A pivot grid. The view's primary `dataSources` entity is the **cell** entity (one record per row×column intersection). `viewConfig` declares the two axes and how the cell record maps onto them.
123
+
124
+ ```json
125
+ {
126
+ "income_statement_matrix": {
127
+ "viewType": "matrix",
128
+ "label": "Income Statement (Matrix)",
129
+ "dataSources": [{
130
+ "entityCode": "balance_register",
131
+ "columns": ["account_id", "period_key", "pl_amount"]
132
+ }],
133
+ "viewConfig": {
134
+ "rowAxis": {
135
+ "kind": "dataset",
136
+ "entity": "account",
137
+ "labelField": "account_name",
138
+ "filter": { "c": "statement", "o": "eq", "v": "IncomeStatement" },
139
+ "sort": [{ "col": "account_code", "dir": "asc" }]
140
+ },
141
+ "colAxis": {
142
+ "kind": "dataset",
143
+ "entity": "accounting_period",
144
+ "labelField": "description",
145
+ "lockedField": "closed",
146
+ "hideEmpty": true,
147
+ "sort": [{ "col": "period_key", "dir": "asc" }]
148
+ },
149
+ "cell": {
150
+ "entity": "balance_register",
151
+ "rowKey": "account_id",
152
+ "colKey": "period_key",
153
+ "fields": ["pl_amount"],
154
+ "editable": false,
155
+ "drill": true
156
+ }
157
+ }
158
+ }
159
+ }
160
+ ```
161
+
162
+ **`rowAxis` / `colAxis`** — what the rows and columns are. Three `kind`s:
163
+
164
+ - `"dataset"` — axis values are an entity's records. `entity` + `labelField` required; optional `keyField` (defaults to the axis entity PK — what the cell's `rowKey`/`colKey` matches), `lockedField` (boolean column → that axis value's cells are read-only), `filter` (same `{c,o,v}` grammar as everywhere — scopes which axis records load), `hideEmpty` (**column axis only** — drop columns with no cell record in any row, e.g. periods with no postings; ignored if it would blank the whole grid), `rangeControl` (**column axis only** — show a From/To range control so the user can narrow the visible columns to a span; persists in view options; composes with `hideEmpty`), `sort` (`[{ col, dir }]`).
165
+ - `"dropdown"` — axis values are a dropdown/flags column's options. `kind` + `column` (an `"entity.column"` reference); optional `values` (`[...]` of option value codes — a subset + order of columns, chosen the way kanban lanes are; omit to show every option in field order). Codes line up by construction.
166
+ - `"date"` — generated date window, **column axis only** (not valid on `rowAxis`). `kind` + `grain: "day"` + `window: "week"` (v1). *Roadmap (designed, not built): arbitrary `range` (`{from, to}`) + `grain` (`day`/`week`/`month`/`quarter`/`year`), surfaced as a date-picker variant of the range bar — lets a time matrix generate its columns instead of needing seeded period records. Don't author against it yet.*
167
+
168
+ > **The matrix is source-agnostic.** A matrix is just config over generic entities: point the cell at *any* dated/keyed entity that fits your model — a pre-aggregated registry (e.g. `gl.balance_register`, fast but period-grained), raw dated movements (any-grain, heavier), or your own table. `gl` is a regular, replaceable module, not a platform requirement; nothing about the matrix knows or needs "gl". Choosing a pre-aggregated source vs raw rows (and the grain) is a per-view config decision, with the usual perf-vs-flexibility trade.
169
+
170
+ **`cell`** — the value-source record at each intersection. `entity` (must match the primary dataSource), `rowKey` / `colKey` (cell columns matching the row/column axis keys), `fields` (cell columns rendered in each cell). Optional: `cardinality: "one"` (v1 default), `editable`, `drill` (read-only cells become clickable → open the cell record and its child sets), `seedFromRow` (`{ cellField: rowAxisField }` copied into new cell records on insert), `seedCurrentUser` (cell fields set to the current user id on insert).
171
+
172
+ ## Columns
173
+
174
+ Each column is either a simple string or an object:
175
+
176
+ ```json
177
+ "columns": [
178
+ "first_name", // just column code, default width
179
+ { "column_cd": "email", "width": 220 }, // with width override
180
+ { "column_cd": "account", "width": 200, "visible": true }
181
+ ]
182
+ ```
183
+
184
+ ## Filters
185
+
186
+ Filters use dForge's canonical JSON filter format — see [filters.md](filters.md) for the full grammar. Keys are `c` (column), `o` (operator), `v` (value) for conditions, and `g` (group operator), `i` (items) for groups.
187
+
188
+ Single condition:
189
+
190
+ ```json
191
+ "filter": { "c": "status", "o": "=", "v": "active" }
192
+ ```
193
+
194
+ Group:
195
+
196
+ ```json
197
+ "filter": {
198
+ "g": "and",
199
+ "i": [
200
+ { "c": "status", "o": "=", "v": "active" },
201
+ { "c": "created_date", "o": ">", "v": "2026-01-01" }
202
+ ]
203
+ }
204
+ ```
205
+
206
+ Do **not** use `op`/`args`/`column`/`value` — that format is rejected.
207
+
208
+ ## Sort (`order`)
209
+
210
+ Sort lives at the **view-def root** (not inside `dataSources[]`) under the key **`order`** — a `string[]` of column codes. Direction is encoded in the string itself:
211
+
212
+ - Bare column code → **ascending** (the default; no prefix needed)
213
+ - Leading `-` → **descending**
214
+
215
+ ```json
216
+ "order": ["last_name", "first_name"] // both ascending
217
+ "order": ["-created_date"] // descending (audit trait provides created_date)
218
+ "order": ["-created_date", "last_name"] // primary desc, then asc tiebreak
219
+ ```
220
+
221
+ The array order is the sort precedence: the first entry is the primary sort key, subsequent entries are tiebreakers.
222
+
223
+ This matches the shape the runtime persists and `data.get` accepts. **Do not** use `"sort"`, `[{ column_cd, direction }]`, or `[{ c, d }]` here — those shapes belong to `query.run` and `report.run` (see [queries.md](queries.md)), not `data_views.json`.
224
+
225
+ ## Multi-source views
226
+
227
+ A view can show data from multiple entities (e.g. a dashboard-style view mixing contacts and activities). Each entry in `dataSources` is independent with its own columns/filter.
228
+
229
+ ```json
230
+ "dataSources": [
231
+ { "entityCode": "contact", "columns": [ /* ... */ ] },
232
+ { "entityCode": "activity", "columns": [ /* ... */ ] }
233
+ ]
234
+ ```
235
+
236
+ Most views only have one source. Order is view-level and applies to the primary (level 0) source.
237
+
238
+ ## Common mistakes
239
+
240
+ - Putting `entityCode` or `columns` at the root — **wrong**. They go inside `dataSources[]`.
241
+ - Using `viewCode` or `view_cd` — **wrong**. The code is the dictionary key.
242
+ - Using `"sort"` for data view ordering — **wrong**. The field is `"order"` and it lives at view-def root (not inside `dataSources[]`).
243
+ - Object-array order like `[{ column_cd: "...", direction: "asc" }]` — **wrong shape for data views**. Use `string[]` with leading `-` for descending.
244
+ - `viewType` is **optional** — omitted or unknown values fall back to `grid` at runtime. Set it explicitly for any non-grid view (`kanban`, `calendar`, `matrix`, …); a `matrix` view also **requires** a `viewConfig` with `rowAxis`/`colAxis`/`cell`.
245
+ - Inventing view types like `"spreadsheet"` or `"table"` — **wrong**. Use `grid`.
@@ -0,0 +1,61 @@
1
+ # Importing a data model from a spreadsheet (.xlsx / .csv)
2
+
3
+ When the user gives you a spreadsheet to turn into a module, you can't read a
4
+ binary `.xlsx` directly — decode it first, then reason over the result.
5
+
6
+ ## Flow
7
+
8
+ 1. **Extract the sheets to a JSON model.** Load the bundled extractor resource
9
+ `dforge://script/xlsx-to-model`, write it to a temp file, and run it:
10
+
11
+ ```bash
12
+ python3 /tmp/xlsx_to_model.py "<path/to/file.xlsx>"
13
+ ```
14
+
15
+ It needs only Python 3 (pure standard library — no `pip install`). It prints
16
+ `{"sheets":[{"name":..,"headers":[..],"rows":[[..],..]}]}`. If it prints
17
+ `{"error":..}` (e.g. Python missing), fall back: ask the user to export each
18
+ sheet as **CSV** (CSV is plain text — read it directly).
19
+
20
+ 2. **Turn the model into a table-spec.** For each sheet:
21
+ - `name` = the sheet name, snake_cased (`"Order Lines"` → `order_lines`).
22
+ - One **column** per header: `name` = header snake_cased; `sampleValues` =
23
+ that column's values from `rows` (a few are enough). Don't set `fieldTypeCd`
24
+ unless you're sure — let the import infer it. Add a `sqlType` hint only when
25
+ the values make the type obvious (all integers → `int`, decimals → `numeric`,
26
+ `YYYY-MM-DD` strings → `date`; remember xlsx **dates arrive as numbers**, so
27
+ lean on the header name, e.g. `*_date`, to spot them).
28
+ - **References (FKs):** when a column looks like a foreign key — header
29
+ `<thing>_id` (or `<thing> id`) where `<thing>` matches another sheet — put it
30
+ in `references: [{ column, toTable }]` instead of as a plain column.
31
+ - Skip an `id` / primary-key column — the `identity` trait provides `{entity}_id`.
32
+
33
+ 3. **Show the proposed entity inventory for sign-off** (same gate as any Phase 1
34
+ scaffold), then call **`dforge_module_import`**:
35
+ - existing module → `{ moduleDir, tables }`
36
+ - new module → add `module: { code, displayName }` (greenfield).
37
+
38
+ 4. **Run `dforge_module_validate`** and refine the generated default grids to
39
+ surface the imported columns.
40
+
41
+ ## With data or empty — both work
42
+
43
+ A sheet may be **populated** (headers + rows) or **structure-only** (headers, no
44
+ data rows). Both produce a valid entity:
45
+
46
+ - **Populated** → use the `sampleValues` for type inference (numbers, dates,
47
+ dropdowns). Best results.
48
+ - **Empty (headers only)** → the extractor returns `"rows": []`. You still get
49
+ one column per header, but with no samples the import defaults most columns to
50
+ `text` — so lean on the **header name** (`email`, `phone`, `*_date`, `*_id` →
51
+ FK) and add `sqlType` hints where the user can tell you the type. Consider
52
+ confirming the inferred types with the user before importing.
53
+
54
+ ## Notes
55
+
56
+ - One worksheet (or one Excel table object) = one entity. Ignore obviously
57
+ non-tabular sheets (dashboards, notes).
58
+ - The extractor returns the first non-empty row as `headers`; if a sheet has
59
+ title rows above the real header, tell the user or adjust the spec by hand.
60
+ - Type inference + the FK+Reference pattern are handled by `dforge_module_import`
61
+ against the `@dforge-core/metadata` registry — you only produce the table-spec.
@@ -0,0 +1,144 @@
1
+ # Field Types Reference
2
+
3
+ The canonical list of field types supported by dForge. **Do not invent new values — the set is fixed** and validated by the platform on module install.
4
+
5
+ Source of truth: `server/database/system-modules/metadata/seed-data/field_types.json` in the dForge repo.
6
+
7
+ > **You usually don't set `dbDatatype`.** For a plain data column, omit it — the field tools derive it from `fieldTypeCd` (the "Common `dbDatatype`" column below): `currency` → `numeric(18,2)`, `text` → `varchar`, `checkbox` → `bool`, etc. An explicit value is never overridden, so only set `dbDatatype` when you need to:
8
+ > - **a hidden FK column** — it has no `fieldTypeCd`, so derivation can't fire; set `dbDatatype: "cuid"` to match the target entity's `identity` PK (see `column-types.md`);
9
+ > - **override the size/precision** — e.g. `varchar(100)` via `maxLen`, or a specific numeric scale.
10
+ >
11
+ > Reference (`columnType: "R"`) and formula (`columnType: "F"`) columns never get a `dbDatatype` at all.
12
+
13
+ ## String-backed
14
+
15
+ | `fieldTypeCd` | `baseDatatypeCd` | Description | Common `dbDatatype` | Params |
16
+ |---|---|---|---|---|
17
+ | `text` | `string` | Single-line text | `varchar` (with `maxLen`) | `maxLen` |
18
+ | `email` | `string` | Email address. Client-side format validation. | `varchar` 250 | — |
19
+ | `phone` | `string` | Phone/telephone. | `varchar` 50 | — |
20
+ | `url` | `string` | Web URL. | `varchar` 500 | — |
21
+ | `textarea` | `string` | Multi-line plain text. | `text` | `defaultLines`, `maxLines` |
22
+ | `richtext` | `string` | Rich text editor (HTML). | `text` | — |
23
+ | `code` | `string` | Code editor with syntax highlighting. | `text` | `language` |
24
+ | `dropdown` | `string` | Fixed list of options. | `varchar` | `options: ["a","b",…]` or `[{value,label,icon,color}]` |
25
+ | `flags` | `string` | Multiple checkboxes with letter flags. | `varchar` | `optionSets`, `style: "buttons"` |
26
+ | `tags` | `string` | Free-form tag list. | `varchar` or `text` | — |
27
+ | `color` | `string` | Color picker. | `varchar(20)` | — |
28
+ | `hidden` | `string` | Internal, never shown in UI. | `varchar` | — |
29
+
30
+ ## Number-backed
31
+
32
+ | `fieldTypeCd` | `baseDatatypeCd` | Description | Common `dbDatatype` | Params |
33
+ |---|---|---|---|---|
34
+ | `number` | `number` | Numeric value. | `int`, `bigint`, `numeric` | `min`, `max`, `scale` |
35
+ | `currency` | `number` | Monetary value. Precision 2. | `numeric(18,2)` | `currency: "USD"`, `min`, `max` |
36
+ | `percent` | `number` | Percentage 0–100. Precision 2. | `numeric(5,2)` | `min`, `max` (default 0–100) |
37
+
38
+ ## Boolean-backed
39
+
40
+ | `fieldTypeCd` | `baseDatatypeCd` | Description | Common `dbDatatype` | Params |
41
+ |---|---|---|---|---|
42
+ | `checkbox` | `bool` | Boolean checkbox. | `bool` | — |
43
+
44
+ > **`checkbox` is the only valid `fieldTypeCd` for boolean fields.** Do not use `bool`, `boolean`, or `toggle` as `fieldTypeCd` — those are not valid field type codes. (`bool` IS the correct `dbDatatype` for this field, but `checkbox` is the only correct `fieldTypeCd`.)
45
+
46
+ ## Date/time-backed
47
+
48
+ | `fieldTypeCd` | `baseDatatypeCd` | Description | Common `dbDatatype` | Params |
49
+ |---|---|---|---|---|
50
+ | `date` | `date` | Calendar date only. | `date` | `min`, `max` (ISO strings or `TODAY()`) |
51
+ | `datetime` | `timestamp` | Date + time with timezone. | `timestamptz` | `min`, `max` |
52
+ | `time` | `time` | Time of day. | `time` | — |
53
+
54
+ ## Binary / file
55
+
56
+ | `fieldTypeCd` | `baseDatatypeCd` | Description | Params |
57
+ |---|---|---|---|
58
+ | `file` | `binary` | Arbitrary file upload. | `accept`, `maxSize` |
59
+ | `image` | `binary` | Image upload with preview. | `maxSize`, `dimensions` |
60
+
61
+ ## Reference / lookup / set
62
+
63
+ | `fieldTypeCd` | `baseDatatypeCd` | `columnType` | Description |
64
+ |---|---|---|---|
65
+ | `lookup` | `guid` | `R` | Reference to another entity (always paired with a hidden FK column — see SKILL.md "FK+Reference pattern") |
66
+ | `user` | `cuid` | `D` | User picker. Writes a user ID directly, no paired FK needed. |
67
+ | `grid` | `set` | `S` | Detail grid of related records (1:N backwards reference). Used with `link` declaring the relation. |
68
+ | `entitylink` | `json` | `D` | Polymorphic link to any entity (stores `{entity, id}` pair). |
69
+
70
+ ## JSON
71
+
72
+ | `fieldTypeCd` | `baseDatatypeCd` | Description |
73
+ |---|---|---|
74
+ | `json` | `json` | JSON editor. Stored in `jsonb`. |
75
+
76
+ ---
77
+
78
+ ## Default values
79
+
80
+ There is **no** `defaultValue` (or `default`) key on an entity data column — it fails schema
81
+ validation (`entity.schema.json` is `additionalProperties: false`). To give a column a default:
82
+
83
+ | Need | Do this |
84
+ |---|---|
85
+ | Literal / computed default (`'draft'`, `TODAY()`) | Carry a `formula` on the column (formula context — uppercase `TODAY()`/`NOW()`), e.g. `"formula": "'draft'"` or `"formula": "TODAY()"`. |
86
+ | Document number (PO-2026-0001) | Declare a `numberSequence` on the entity — auto-fills on insert. |
87
+ | Anything set at create time | Set it in an action/trigger DSL (`execute:` uses lowercase `now()`). |
88
+
89
+ > `defaultValue` **is** valid on **module settings** (`settings.json`) — that's the only place it
90
+ > belongs. Don't carry the settings habit over to entity fields.
91
+
92
+ ---
93
+
94
+ ## Common mistakes
95
+
96
+ > **Wrong key name:** `fieldType` is a C# navigation property on the server model — it is not a valid JSON key in entity definitions. Always use `fieldTypeCd` (the string code). Using `fieldType: { ... }` or `fieldType: "date"` causes the platform to silently ignore the field type, producing null constraint errors on save.
97
+
98
+ > **No `defaultValue` on entity fields:** `defaultValue` is a *settings* key, not a column key — on a field it fails schema validation. Set a column default with a `formula`, a `numberSequence`, or DSL logic (see "Default values" above).
99
+
100
+ ### Wrong `dbDatatype` values
101
+
102
+ These are SQL type names LLMs tend to use for `dbDatatype`. **They are all wrong.** The right values come from the tables above.
103
+
104
+ | Wrong `dbDatatype` | Right `dbDatatype` | Used with `fieldTypeCd` |
105
+ |---|---|---|
106
+ | `datetime` | `timestamptz` | `datetime` |
107
+ | `timestamp` | `timestamptz` | `datetime` |
108
+ | `boolean` | `bool` | `checkbox` |
109
+ | `string` | `varchar` (with `maxLen`) or `text` | `text`, `textarea`, `dropdown`, … |
110
+ | `integer` | `int` or `bigint` | `number` |
111
+ | `float`, `double`, `decimal` | `numeric` | `number`, `currency`, `percent` |
112
+ | `number` | `int`, `bigint`, or `numeric` | `number`, `currency`, `percent` |
113
+
114
+ ### Wrong `fieldTypeCd` values
115
+
116
+ These are the field type names LLMs tend to invent. **They are all wrong.**
117
+
118
+ | Wrong | Right |
119
+ |---|---|
120
+ | key `fieldType: { fieldTypeCd: "date", ... }` | key `fieldTypeCd: "date"` (plain string) |
121
+ | key `fieldType: "date"` | key `fieldTypeCd: "date"` |
122
+ | `bool`, `boolean` | `checkbox` |
123
+ | `integer` | `number` |
124
+ | `float` | `number` |
125
+ | `decimal` | `number` with `scale` param |
126
+ | `datePicker` | `date` |
127
+ | `timestamp` | `datetime` |
128
+ | `money` | `currency` |
129
+ | `select`, `enum` | `dropdown` |
130
+ | `multiselect` | `flags` or `tags` |
131
+ | `autocomplete` | `lookup` |
132
+ | `reference` | `lookup` |
133
+ | `relation` | `lookup` or `grid` |
134
+ | `link` | `url` |
135
+ | `picture`, `photo` | `image` |
136
+ | `attachment` | `file` |
137
+ | `multiline` | `textarea` |
138
+ | `richText`, `markdown` | `richtext` |
139
+ | `userPicker` | `user` |
140
+ | `phoneNumber` | `phone` |
141
+
142
+ ## When in doubt
143
+
144
+ If the user describes a field type that isn't in this list (e.g. "rating stars", "geo location", "signature pad"), those field types **do not currently exist**. Do not fabricate a `fieldTypeCd` value. Tell the user what's available and ask how they'd like to model it with existing types (e.g. rating as `number` with `min: 1, max: 5`, geo location as two `number` columns or a `json` column).