@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.
- package/CHANGELOG.md +132 -0
- package/README.md +84 -27
- package/dist/server.js +2957 -616
- package/docs/creating-modules.md +11 -4
- package/package.json +11 -6
- package/resources/docs/conventions.md +22 -16
- package/resources/docs/dsl-reference.md +767 -0
- package/resources/schemas/entity.schema.json +8 -1
- package/resources/schemas/print-templates.schema.json +82 -0
- package/resources/schemas/reports.schema.json +4 -0
- package/resources/schemas/triggers.schema.json +59 -0
- package/skills/dforge-mcp-author/SKILL.md +269 -69
- package/skills/dforge-mcp-author/examples/matrix-budget/README.md +43 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/entities/budget_category.json +24 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/entities/budget_line.json +56 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/manifest.json +30 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/security/roles.json +9 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/seed-data/01-categories.json +8 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/ui/data_views.json +42 -0
- package/skills/dforge-mcp-author/examples/simple-todo/README.md +38 -0
- package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_item.json +83 -0
- package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_list.json +43 -0
- package/skills/dforge-mcp-author/examples/simple-todo/logic/actions/mark_done.dsl +6 -0
- package/skills/dforge-mcp-author/examples/simple-todo/manifest.json +32 -0
- package/skills/dforge-mcp-author/examples/simple-todo/security/roles.json +10 -0
- package/skills/dforge-mcp-author/examples/simple-todo/seed-data/01-lists.json +17 -0
- package/skills/dforge-mcp-author/examples/simple-todo/ui/actions.json +11 -0
- package/skills/dforge-mcp-author/examples/simple-todo/ui/data_views.json +35 -0
- package/skills/dforge-mcp-author/examples/simple-todo/ui/menus.json +28 -0
- package/skills/dforge-mcp-author/references/action-dsl.md +397 -0
- package/skills/dforge-mcp-author/references/column-types.md +168 -0
- package/skills/dforge-mcp-author/references/conventions.md +177 -0
- package/skills/dforge-mcp-author/references/data-migration.md +270 -0
- package/skills/dforge-mcp-author/references/data-views.md +243 -0
- package/skills/dforge-mcp-author/references/excel-import.md +61 -0
- package/skills/dforge-mcp-author/references/field-types.md +144 -0
- package/skills/dforge-mcp-author/references/filters.md +326 -0
- package/skills/dforge-mcp-author/references/flags.md +73 -0
- package/skills/dforge-mcp-author/references/formulas.md +206 -0
- package/skills/dforge-mcp-author/references/jobs.md +149 -0
- package/skills/dforge-mcp-author/references/manifest.md +123 -0
- package/skills/dforge-mcp-author/references/menus.md +164 -0
- package/skills/dforge-mcp-author/references/number-sequences.md +117 -0
- package/skills/dforge-mcp-author/references/print-templates.md +159 -0
- package/skills/dforge-mcp-author/references/queries.md +312 -0
- package/skills/dforge-mcp-author/references/reports.md +398 -0
- package/skills/dforge-mcp-author/references/schema-import.md +331 -0
- package/skills/dforge-mcp-author/references/security.md +244 -0
- package/skills/dforge-mcp-author/references/settings.md +120 -0
- package/skills/dforge-mcp-author/references/traits.md +153 -0
- package/skills/dforge-mcp-author/references/translations.md +158 -0
- package/skills/dforge-mcp-author/references/validation-checklist.md +183 -0
- 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"`.
|