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

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 +132 -0
  2. package/README.md +84 -27
  3. package/dist/server.js +2957 -616
  4. package/docs/creating-modules.md +11 -4
  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 +269 -69
  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 +243 -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 +183 -0
  53. package/skills/dforge-mcp-author/scripts/xlsx_to_model.py +198 -0
@@ -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).
@@ -0,0 +1,326 @@
1
+ # Filter Format Reference
2
+
3
+ dForge uses **one canonical JSON filter format everywhere** — folder row filters, data view filters, API filters, report dataset filters, and user ad-hoc filters. Learn it once, use it everywhere.
4
+
5
+ This is the single filter format used across the entire dForge platform.
6
+
7
+ ## Basic structure
8
+
9
+ A filter is either a **single condition** or a **group** of conditions.
10
+
11
+ ### Condition — single comparison
12
+
13
+ ```json
14
+ { "c": "status", "o": "=", "v": "draft" }
15
+ ```
16
+
17
+ | Key | Type | Description |
18
+ |---|---|---|
19
+ | `c` | string | Column code (field name on the entity) |
20
+ | `o` | string | Operator (see table below) |
21
+ | `v` | any | Value to compare against |
22
+
23
+ ### Group — logical combination
24
+
25
+ ```json
26
+ {
27
+ "g": "and",
28
+ "i": [
29
+ { "c": "status", "o": "=", "v": "draft" },
30
+ { "c": "amount", "o": ">", "v": 1000 }
31
+ ]
32
+ }
33
+ ```
34
+
35
+ | Key | Type | Description |
36
+ |---|---|---|
37
+ | `g` | string | Group operator: `and`, `or`, `!and` (NAND), `!or` (NOR) |
38
+ | `i` | array | Items — conditions and/or nested groups |
39
+
40
+ Groups can nest to arbitrary depth.
41
+
42
+ ## Operators
43
+
44
+ ### Condition operators
45
+
46
+ | Operator | Description | Example value |
47
+ |---|---|---|
48
+ | `=` or `eq` | Equal | `"draft"` |
49
+ | `!=` or `notEq` | Not equal | `"cancelled"` |
50
+ | `>` or `gr` | Greater than | `1000` |
51
+ | `>=` or `grEq` | Greater than or equal | `1000` |
52
+ | `<` or `less` | Less than | `100` |
53
+ | `<=` or `lessEq` | Less than or equal | `100` |
54
+ | `between` or `btw` | Between (inclusive) | `[100, 500]` |
55
+ | `!between` or `nBetween` | Not between | `[100, 500]` |
56
+ | `contains` | Contains substring | `"acme"` |
57
+ | `!contains` or `nContain` | Does not contain | `"test"` |
58
+ | `start` | Starts with | `"INV-"` |
59
+ | `!start` or `nStart` | Does not start with | `"DRAFT-"` |
60
+ | `end` | Ends with | `".pdf"` |
61
+ | `!end` or `nEnd` | Does not end with | `".tmp"` |
62
+ | `mask` | Pattern match (SQL LIKE) | `"INV-2025-%"` |
63
+ | `!mask` or `nMask` | Does not match pattern | `"TEST-%"` |
64
+ | `null` or `empty` | Is null/empty | _(no `v` needed)_ |
65
+ | `!null` or `nEmpty` | Is not null | _(no `v` needed)_ |
66
+ | `in` | In a set | `["draft", "pending"]` |
67
+ | `!in` or `nIn` | Not in a set | `["cancelled", "deleted"]` |
68
+ | `eqRef` | Equal to another column | `"other_column_cd"` |
69
+ | `f` | Function (dynamic runtime value) | `{ "fn": "isToday" }` |
70
+
71
+ ### Function operator — dynamic runtime values
72
+
73
+ The function operator `f` evaluates a named function at query time instead of comparing against a static value. The value must be an object: `{ "fn": "<functionId>" }`.
74
+
75
+ | Function | Column types | Description |
76
+ |---|---|---|
77
+ | `isToday` | date, timestamp | `column = CURRENT_DATE` |
78
+ | `isYesterday` | date, timestamp | `column = CURRENT_DATE - 1` |
79
+ | `isThisWeek` | date, timestamp | Within current ISO week |
80
+ | `isThisMonth` | date, timestamp | Within current month |
81
+ | `isThisYear` | date, timestamp | Within current year |
82
+ | `isPast` | date, timestamp | `column < NOW()` |
83
+ | `isFuture` | date, timestamp | `column > NOW()` |
84
+ | `isCurrentUser` | guid | `column = <logged-in user ID>` |
85
+
86
+ ```json
87
+ { "c": "due_date", "o": "f", "v": { "fn": "isPast" } }
88
+ ```
89
+
90
+ ```json
91
+ { "c": "owner_id", "o": "f", "v": { "fn": "isCurrentUser" } }
92
+ ```
93
+
94
+ Function conditions require **no value input** — the function provides the comparison logic at runtime.
95
+
96
+ **Functions are valid only with the `f` operator.** They cannot be used as the value of `<`, `>`, `=`, `!=`, `between`, or any other comparison operator — each function already encodes its own comparison (e.g. `isPast` means "column < NOW()"). If you need to combine a dynamic-date check with another condition, put them as separate items in an `and`/`or` group:
97
+
98
+ ```json
99
+ {
100
+ "g": "and",
101
+ "i": [
102
+ { "c": "due_date", "o": "f", "v": { "fn": "isPast" } },
103
+ { "c": "status", "o": "!=", "v": "done" }
104
+ ]
105
+ }
106
+ ```
107
+
108
+ These are **wrong** and will be rejected:
109
+
110
+ ```json
111
+ { "c": "due_date", "o": "<", "v": "@TODAY" }
112
+ { "c": "due_date", "o": "<", "v": { "fn": "isToday" } }
113
+ ```
114
+
115
+ ### Group operators
116
+
117
+ | Operator | SQL equivalent |
118
+ |---|---|
119
+ | `and` | `A AND B AND C` |
120
+ | `or` | `A OR B OR C` |
121
+ | `!and` | `NOT (A AND B AND C)` — at least one is false |
122
+ | `!or` | `NOT (A OR B OR C)` — none are true |
123
+
124
+ ## Where filters are used
125
+
126
+ ### 1. Folder row filters (`ui/folders.json`)
127
+
128
+ Scope which records are visible in a subfolder. Used in `rowFilter` property on entity membership:
129
+
130
+ ```json
131
+ "stock": {
132
+ "viewName": "default",
133
+ "quickAdd": true,
134
+ "rowFilter": { "c": "warehouse_id", "o": "eq", "v": 2001 }
135
+ }
136
+ ```
137
+
138
+ Generates: only stock records where `warehouse_id = 2001` appear in this folder.
139
+
140
+ For compound filters on a folder:
141
+
142
+ ```json
143
+ "rowFilter": {
144
+ "g": "and",
145
+ "i": [
146
+ { "c": "warehouse_id", "o": "eq", "v": 2001 },
147
+ { "c": "status", "o": "!=", "v": "archived" }
148
+ ]
149
+ }
150
+ ```
151
+
152
+ ### 2. Data view filters (`ui/data_views.json`)
153
+
154
+ Pre-filter which records appear in a data view:
155
+
156
+ ```json
157
+ {
158
+ "active_leads": {
159
+ "viewType": "grid",
160
+ "label": "Active Leads",
161
+ "dataSources": [{
162
+ "entityCode": "lead",
163
+ "columns": ["name", "email", "stage", "owner_id"],
164
+ "filter": {
165
+ "g": "and",
166
+ "i": [
167
+ { "c": "stage", "o": "!=", "v": "Closed Lost" },
168
+ { "c": "stage", "o": "!=", "v": "Converted" }
169
+ ]
170
+ },
171
+ "sort": [{ "column_cd": "created_date", "direction": "desc" }]
172
+ }]
173
+ }
174
+ }
175
+ ```
176
+
177
+ ### 3. Report dataset filters
178
+
179
+ Same format inside report dataset definitions:
180
+
181
+ ```json
182
+ "datasets": {
183
+ "ds_pipeline": {
184
+ "entityCode": "opportunity",
185
+ "columns": ["stage", "total_amount"],
186
+ "filter": {
187
+ "g": "and",
188
+ "i": [
189
+ { "c": "stage", "o": "!in", "v": ["Closed Won", "Closed Lost"] },
190
+ { "c": "total_amount", "o": ">", "v": 0 }
191
+ ]
192
+ },
193
+ "groupBy": ["stage"],
194
+ "aggregations": {
195
+ "total_value": { "func": "sum", "column": "total_amount" }
196
+ }
197
+ }
198
+ }
199
+ ```
200
+
201
+ ### 4. API filters (`data.get` calls)
202
+
203
+ Same format passed as the `filter` parameter in RPC calls:
204
+
205
+ ```json
206
+ {
207
+ "method": "data.get",
208
+ "params": {
209
+ "entityCode": "contact",
210
+ "filter": { "c": "account_id", "o": "=", "v": "some-uuid" },
211
+ "columns": ["first_name", "last_name", "email"]
212
+ }
213
+ }
214
+ ```
215
+
216
+ ### 5. Action DSL (`query()` is different!)
217
+
218
+ **Important**: the `query()` function in action DSL uses **raw parameterized SQL**, NOT the JSON filter format:
219
+
220
+ ```javascript
221
+ // This is SQL, not a JSON filter
222
+ var results = query('SELECT * FROM crm.contact WHERE account_id = @accId AND status = @status', {
223
+ accId: [account_id],
224
+ status: 'active'
225
+ })
226
+ ```
227
+
228
+ The JSON filter format is only used in **declarative contexts** (folders, views, reports, API calls). Action DSL uses SQL directly.
229
+
230
+ ## Common examples
231
+
232
+ ### Filter by status (single condition)
233
+
234
+ ```json
235
+ { "c": "status", "o": "=", "v": "active" }
236
+ ```
237
+
238
+ ### Exclude multiple statuses
239
+
240
+ ```json
241
+ { "c": "status", "o": "!in", "v": ["cancelled", "deleted", "archived"] }
242
+ ```
243
+
244
+ ### Date range
245
+
246
+ ```json
247
+ {
248
+ "g": "and",
249
+ "i": [
250
+ { "c": "created_date", "o": ">=", "v": "2026-01-01" },
251
+ { "c": "created_date", "o": "<", "v": "2026-04-01" }
252
+ ]
253
+ }
254
+ ```
255
+
256
+ Or using `between`:
257
+
258
+ ```json
259
+ { "c": "amount", "o": "between", "v": [1000, 5000] }
260
+ ```
261
+
262
+ ### Non-null check
263
+
264
+ ```json
265
+ { "c": "email", "o": "!null" }
266
+ ```
267
+
268
+ ### String matching
269
+
270
+ ```json
271
+ { "c": "invoice_number", "o": "start", "v": "INV-2026-" }
272
+ ```
273
+
274
+ ### Complex: active records in sales or marketing
275
+
276
+ ```json
277
+ {
278
+ "g": "and",
279
+ "i": [
280
+ { "c": "is_active", "o": "=", "v": true },
281
+ {
282
+ "g": "or",
283
+ "i": [
284
+ { "c": "department", "o": "=", "v": "sales" },
285
+ { "c": "department", "o": "=", "v": "marketing" }
286
+ ]
287
+ }
288
+ ]
289
+ }
290
+ ```
291
+
292
+ ### Column-to-column comparison
293
+
294
+ ```json
295
+ { "c": "ship_date", "o": "eqRef", "v": "order_date" }
296
+ ```
297
+
298
+ Generates: `WHERE ship_date = order_date`
299
+
300
+ ## Filter composition at query time
301
+
302
+ All filter layers are **ANDed together** automatically:
303
+
304
+ ```
305
+ Effective filter = folder_row_filter AND view_filter AND api_filter AND user_ad_hoc_filter
306
+ ```
307
+
308
+ The user never sees a record unless it passes **all** layers. You don't need to duplicate folder filters in view filters — they compose automatically.
309
+
310
+ ## Rules
311
+
312
+ - **`null` filter** (or omitted) means no filtering — show everything the user has access to.
313
+ - **Single condition** is valid without a group wrapper.
314
+ - Use **short operators** (`=`, `!=`, `>`) or **enum keys** (`eq`, `notEq`, `gr`) — both work.
315
+ - For folder `rowFilter` in module packages, prefer the short compact form: `{ "c": "...", "o": "eq", "v": ... }`.
316
+ - Dates are ISO strings: `"2026-01-01"`.
317
+ - Arrays for `in`/`between`: `["a", "b"]` or `[100, 500]`.
318
+
319
+ ## Common mistakes
320
+
321
+ - Using the JSON filter format inside `query()` in DSL actions — **wrong**. `query()` uses raw SQL.
322
+ - Wrapping a single condition in a group unnecessarily — **valid but noisy**. A bare condition is fine.
323
+ - Using `"operator": "equals"` — **wrong**. Use `"o": "="` or `"o": "eq"`.
324
+ - Using `"column": "status"` — **wrong**. Use `"c": "status"`.
325
+ - Forgetting that `null` means "no filter" (not "filter by null") — to filter for null values, use `{ "c": "field", "o": "null" }`.
326
+ - Putting a `{ "fn": "..." }` object (or `@TODAY`/`@CURRENT_USER` placeholder) as the value of a normal comparison like `<`, `>`, `=` — **wrong**. Functions are standalone conditions that only work with `"o": "f"`.
@@ -0,0 +1,73 @@
1
+ # Column Flags Reference
2
+
3
+ Column flags are single-letter codes concatenated into a string. Order doesn't matter — `"VEM"` and `"EMV"` are equivalent.
4
+
5
+ Valid flags regex: `^[VIEMH]*$`
6
+
7
+ ## Flag letters
8
+
9
+ | Letter | Name | Meaning |
10
+ |---|---|---|
11
+ | `V` | Visible | Column appears in grid / form / card views. Omit to hide from standard UI. |
12
+ | `I` | Internal | Auto-managed by the platform. Used on PK columns, audit timestamps, system fields. Not shown in UI, not directly editable by users. |
13
+ | `E` | Editable | User can modify the value in the UI. Omit for read-only. |
14
+ | `M` | Mandatory | Required (NOT NULL + UI enforcement). |
15
+ | `H` | Hidden | Permanently hidden — never shown, not even in admin metadata views. Stronger than omitting `V`. |
16
+
17
+ **Only these 5 letters are valid.** The platform rejects any other characters.
18
+
19
+ ## Common column flag combinations
20
+
21
+ | Flags | Meaning | Use case |
22
+ |---|---|---|
23
+ | `"VEM"` | Visible, Editable, Mandatory | Required field shown in UI (most business fields) |
24
+ | `"VE"` | Visible, Editable, optional | Nullable field |
25
+ | `"V"` | Visible, read-only | Display-only, like formula columns or computed values |
26
+ | `"EM"` | Editable, Mandatory, **not visible** | Hidden FK columns in the FK+Reference pattern |
27
+ | `"I"` | Internal | Trait-provided columns (PK, audit timestamps). Platform-managed. |
28
+ | `""` (empty) | No flags | Rare — column exists but is invisible and not editable |
29
+
30
+ ## Rules
31
+
32
+ 1. **Hidden FK columns**: always `"EM"`. They must be writeable (E) and required (M), but never shown (no V).
33
+ 2. **Visible Reference columns**: always `"VEM"`. The user sees and edits the lookup picker.
34
+ 3. **Formula columns**: usually `"V"` (visible, read-only). The user never edits them.
35
+ 4. **Trait columns** (PK, audit): use `"I"` (internal). These are set automatically by `traits: ["identity", "audit"]`.
36
+ 5. **Don't use `I` on your own columns** — it's reserved for trait-provided and platform-managed columns.
37
+ 6. **Don't mix `I` with `V` or `E`** — internal columns are not user-facing.
38
+
39
+ ## Entity rights flags (different namespace — roles only)
40
+
41
+ These letters appear in role `rights` declarations, **NOT** on column flags. Don't confuse them:
42
+
43
+ | Letter | Permission | Used on |
44
+ |---|---|---|
45
+ | `S` | Select (read rows) | Entity in role `rights` |
46
+ | `I` | Insert (create rows) | Entity in role `rights` |
47
+ | `U` | Update (modify rows) | Entity in role `rights` |
48
+ | `D` | Delete (remove rows) | Entity in role `rights` |
49
+ | `C` | Clone (duplicate row) | Entity in role `rights` |
50
+ | `E` | Execute | Actions, reports, folders in role `rights` |
51
+
52
+ So `"SIUD"` on a role grants full CRUD; `"SI"` grants read + create only.
53
+
54
+ **Note**: the `I` in column flags (Internal) and the `I` in role rights (Insert) are different concepts using the same letter. Context makes them unambiguous — column flags go on entity fields, role rights go on `security/roles.json`.
55
+
56
+ ## How uniqueness, PKs, and search work
57
+
58
+ These are **NOT** flag letters. They're separate properties on the column definition:
59
+
60
+ | Need | How to declare | NOT this |
61
+ |---|---|---|
62
+ | Primary key | `"isPk": true` (or use `identity` trait) | NOT a `P` flag |
63
+ | Unique column | Add a unique index in the entity's `indexes` block, or `"isUnique": true` | NOT a `U` flag |
64
+ | Searchable | Configure in data view columns or search settings | NOT an `S` flag |
65
+
66
+ ## Mistakes to avoid
67
+
68
+ - Using `"U"` for unique — **not a valid flag**. Use `"isUnique": true` or indexes.
69
+ - Using `"S"` for searchable — **not a valid flag**.
70
+ - Using `"P"` for primary key — **not a valid flag**. Use `"isPk": true` or the `identity` trait.
71
+ - Using `"VEM"` on trait-provided columns (PK, audit) — **don't**. Traits set their own flags (`"I"`).
72
+ - Using `"I"` on business columns — **don't**. `I` means "platform-managed"; regular business columns use `V`/`E`/`M`.
73
+ - Using long names like `"Visible,Editable"` — **wrong**. Single letters concatenated: `"VE"`.