@dforge-core/dforge-mcp 0.2.0 → 0.2.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.
- package/CHANGELOG.md +187 -0
- package/dist/server.js +355 -320
- package/package.json +46 -46
- package/resources/docs/conventions.md +12 -9
- package/resources/docs/dsl-reference.md +48 -12
- package/resources/schemas/domains.schema.json +78 -0
- package/resources/schemas/entity.schema.json +20 -2
- package/resources/schemas/triggers.schema.json +6 -6
- package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_item.json +1 -0
- package/skills/dforge-mcp-author/references/action-dsl.md +70 -3
- package/skills/dforge-mcp-author/references/column-types.md +11 -4
- package/skills/dforge-mcp-author/references/conventions.md +4 -2
- package/skills/dforge-mcp-author/references/data-views.md +2 -2
- package/skills/dforge-mcp-author/references/field-types.md +1 -1
- package/skills/dforge-mcp-author/references/flags.md +37 -6
- package/skills/dforge-mcp-author/references/formulas.md +22 -4
- package/skills/dforge-mcp-author/references/schema-import.md +4 -2
- package/skills/dforge-mcp-author/references/validation-checklist.md +3 -2
- package/skills/dforge-module-build/SKILL.md +12 -5
|
@@ -67,8 +67,8 @@ If none of an entity's fields are visible (or only its set/child-collection colu
|
|
|
67
67
|
```jsonc
|
|
68
68
|
// WRONG — grid over an entity whose fields are all hidden (no "V")
|
|
69
69
|
"fields": {
|
|
70
|
-
"code": { "fieldTypeCd": "text", "flags": "EM" }, // E=editable, M
|
|
71
|
-
"lines": { "columnType": "S", "flags": "
|
|
70
|
+
"code": { "fieldTypeCd": "text", "flags": "EM" }, // E=editable, M=required, but no V
|
|
71
|
+
"lines": { "columnType": "S", "flags": "VE" } // visible, but a SET — doesn't count for a grid
|
|
72
72
|
}
|
|
73
73
|
// RIGHT — at least one visible scalar field
|
|
74
74
|
"fields": {
|
|
@@ -72,7 +72,7 @@ Source of truth: `server/database/system-modules/metadata/seed-data/field_types.
|
|
|
72
72
|
| `user` | `cuid` | `D` | User picker. Writes a user ID directly, no paired FK needed. |
|
|
73
73
|
| `grid` | `set` | `S` | Detail grid of related records (1:N backwards reference). Used with `link` declaring the relation. |
|
|
74
74
|
| `list` | `set` | `S` | Detail list — same 1:N set as `grid`, rendered as a card list instead of a table. |
|
|
75
|
-
| `entitylink` | `json` | `D` | Polymorphic link to any entity
|
|
75
|
+
| `entitylink` | `json` | `D` | Polymorphic link to any entity — stores the **resolved entity id** plus the record's PK (as strings) and a display caption. Populate from action DSL with the `entityLink('entityCd', record, description?)` built-in; pass a qualified entity code (see `action-dsl.md`). |
|
|
76
76
|
|
|
77
77
|
## JSON
|
|
78
78
|
|
|
@@ -11,26 +11,54 @@ Valid flags regex: `^[VIEMH]*$`
|
|
|
11
11
|
| `V` | Visible | Column appears in grid / form / card views. Omit to hide from standard UI. |
|
|
12
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
13
|
| `E` | Editable | User can modify the value in the UI. Omit for read-only. |
|
|
14
|
-
| `M` | Mandatory | Required
|
|
14
|
+
| `M` | Mandatory | Required. Resolved into `isNullable: false` at install — see below. |
|
|
15
15
|
| `H` | Hidden | Permanently hidden — never shown, not even in admin metadata views. Stronger than omitting `V`. |
|
|
16
16
|
|
|
17
17
|
**Only these 5 letters are valid.** The platform rejects any other characters.
|
|
18
18
|
|
|
19
|
+
## `M` is the platform's one notion of "required"
|
|
20
|
+
|
|
21
|
+
At install, `MandatoryFlagNormalizer` folds `M` into `isNullable: false` before DDL
|
|
22
|
+
generation. Everything downstream reads only `isNullable`: the `NOT NULL` in the
|
|
23
|
+
generated DDL, `data.insert`'s required-column check, and the red asterisk on the
|
|
24
|
+
client. There is no second signal.
|
|
25
|
+
|
|
26
|
+
Four consequences worth knowing before you type a flag:
|
|
27
|
+
|
|
28
|
+
- **`M` makes the column `NOT NULL`.** It is not decoration and not a UI hint. Put it
|
|
29
|
+
on a column only when a row is genuinely meaningless without the value.
|
|
30
|
+
- **`M` together with `"isNullable": true` is a hard error at pack time**, naming the
|
|
31
|
+
entity and every offending field. It's a contradiction only you can resolve: drop
|
|
32
|
+
`M` if the column is optional, or drop `isNullable` if it is required.
|
|
33
|
+
- **`M` is inert on virtual (`R`/`S`/`F`) and identity columns** — nullability is
|
|
34
|
+
meaningless where there is no physical column. It won't fail, but `VEM` on a
|
|
35
|
+
Reference over an *optional* FK reads as a required field to the next author, which
|
|
36
|
+
is exactly how these get out of step. Match the Reference to its FK.
|
|
37
|
+
- **Adding `M` to a shipped field aborts the upgrade** on any tenant whose existing
|
|
38
|
+
rows leave it empty — rolled back, naming the column and its NULL row count. Give
|
|
39
|
+
the column a `params.serverDefault` if the install should backfill it. Removing `M`
|
|
40
|
+
is always safe.
|
|
41
|
+
|
|
42
|
+
Omitting both `M` and `isNullable` leaves the column nullable — that is the default.
|
|
43
|
+
|
|
19
44
|
## Common column flag combinations
|
|
20
45
|
|
|
21
46
|
| Flags | Meaning | Use case |
|
|
22
47
|
|---|---|---|
|
|
23
|
-
| `"VEM"` | Visible, Editable, Mandatory | Required field shown in UI
|
|
24
|
-
| `"VE"` | Visible, Editable, optional | Nullable field |
|
|
48
|
+
| `"VEM"` | Visible, Editable, Mandatory | Required field shown in UI |
|
|
49
|
+
| `"VE"` | Visible, Editable, optional | Nullable field — the common case for most business fields |
|
|
25
50
|
| `"V"` | Visible, read-only | Display-only, like formula columns or computed values |
|
|
26
|
-
| `"
|
|
51
|
+
| `"E"` | Editable, **not visible** | Hidden FK column of an *optional* relation |
|
|
52
|
+
| `"EM"` | Editable, Mandatory, **not visible** | Hidden FK column of a *required* relation |
|
|
27
53
|
| `"I"` | Internal | Trait-provided columns (PK, audit timestamps). Platform-managed. |
|
|
28
54
|
| `""` (empty) | No flags | Rare — column exists but is invisible and not editable |
|
|
29
55
|
|
|
30
56
|
## Rules
|
|
31
57
|
|
|
32
|
-
1. **Hidden FK columns**:
|
|
33
|
-
|
|
58
|
+
1. **Hidden FK columns**: `"E"`, or `"EM"` when the relationship is genuinely required.
|
|
59
|
+
Writeable (E), never shown (no V). `M` here is what makes the FK `NOT NULL`.
|
|
60
|
+
2. **Visible Reference columns**: `"VE"`, or `"VEM"` when the FK is required — **keep the
|
|
61
|
+
two halves in step**. `dforge_entity_reference_add({ required })` sets both for you.
|
|
34
62
|
3. **Formula columns**: usually `"V"` (visible, read-only). The user never edits them.
|
|
35
63
|
4. **Trait columns** (PK, audit): use `"I"` (internal). These are set automatically by `traits: ["identity", "audit"]`.
|
|
36
64
|
5. **Don't use `I` on your own columns** — it's reserved for trait-provided and platform-managed columns.
|
|
@@ -68,6 +96,9 @@ These are **NOT** flag letters. They're separate properties on the column defini
|
|
|
68
96
|
- Using `"U"` for unique — **not a valid flag**. Use `"isUnique": true` or indexes.
|
|
69
97
|
- Using `"S"` for searchable — **not a valid flag**.
|
|
70
98
|
- Using `"P"` for primary key — **not a valid flag**. Use `"isPk": true` or the `identity` trait.
|
|
99
|
+
- Declaring `M` **and** `"isNullable": true` on the same field — a contradiction that fails the pack.
|
|
100
|
+
- Reaching for `"VEM"` by default — `M` now means `NOT NULL`. Most business fields are `"VE"`.
|
|
101
|
+
- Putting `M` on a Reference whose hidden FK is optional — inert, but it misreports the field as required.
|
|
71
102
|
- Using `"VEM"` on trait-provided columns (PK, audit) — **don't**. Traits set their own flags (`"I"`).
|
|
72
103
|
- Using `"I"` on business columns — **don't**. `I` means "platform-managed"; regular business columns use `V`/`E`/`M`.
|
|
73
104
|
- Using long names like `"Visible,Editable"` — **wrong**. Single letters concatenated: `"VE"`.
|
|
@@ -26,6 +26,12 @@ The same grammar applies everywhere.
|
|
|
26
26
|
`CONTAINS` / `STARTS_WITH` / `ENDS_WITH` are infix operators, not functions — write
|
|
27
27
|
`[note] CONTAINS 'urgent'`, never `CONTAINS([note], 'urgent')`.
|
|
28
28
|
|
|
29
|
+
**Comparison is null-safe and two-valued in every context.** `null = null` is true, and
|
|
30
|
+
`[x] != null` is true for any non-null `x`. There is no SQL-style third "unknown" answer,
|
|
31
|
+
so `IF([completed_date] != null, 100, 0)` gives the same result on the record card and in
|
|
32
|
+
a report. At SQL time `=` / `!=` translate to `IS NOT DISTINCT FROM` / `IS DISTINCT FROM`
|
|
33
|
+
to preserve that.
|
|
34
|
+
|
|
29
35
|
## Examples
|
|
30
36
|
|
|
31
37
|
```
|
|
@@ -156,8 +162,9 @@ like any other boolean. A bare `date` column is treated as a **local calendar da
|
|
|
156
162
|
- `AVG`, `MIN`, `MAX` similarly
|
|
157
163
|
|
|
158
164
|
> ⚠️ **Set aggregation is NOT a Formula (`F`) feature.** `SUM([set].[field])` in an `F` column is
|
|
159
|
-
> unsupported by
|
|
160
|
-
>
|
|
165
|
+
> unsupported by **either** engine — the formula runtime has no `SUM`/`COUNT`/`AVG`, and nav
|
|
166
|
+
> resolution walks N:1 references only, never a 1:N set, however many hops it is given. It
|
|
167
|
+
> silently renders **empty** (no error).
|
|
161
168
|
|
|
162
169
|
Put set aggregations in a **Generated (`G`) column** instead. The installer detects the set
|
|
163
170
|
navigation and maintains the value with a database trigger on the child table. The aggregated
|
|
@@ -186,9 +193,20 @@ and the JSON shape: `column-types.md` → "Roll-up totals over child rows — us
|
|
|
186
193
|
[account].[primary_contact].[phone] -- chained
|
|
187
194
|
```
|
|
188
195
|
|
|
189
|
-
Navigation works through `columnType: "R"` columns (reference columns). Chains of length 1 are **synchronous** and resolved instantly. Chains of length ≥ 2 are **asynchronous** — the formula engine resolves them after the initial data load.
|
|
196
|
+
Navigation works through `columnType: "R"` columns (reference columns) — every step except the last must be a Reference, and the last reads a field on the final referenced record. Chains of length 1 are **synchronous** and resolved instantly. Chains of length ≥ 2 are **asynchronous** — the formula engine resolves them after the initial data load.
|
|
197
|
+
|
|
198
|
+
**SQL-time (reports, filters, sorts).** Multi-hop navigation works here too: QueryBuilder pre-scans the formula and registers one `LEFT JOIN` per hop, then the translator resolves the path against the final alias. A nav path may also land on a *formula* column of the referenced entity — that formula is translated against the referenced entity's alias, and its own hops are joined off that alias. The same nav map drives formula columns in filters and ORDER BY, so filtering and sorting a report by a nav-formula column works.
|
|
199
|
+
|
|
200
|
+
Hops are counted from the query's **root** entity and capped at `MaxNavigationDepth`, whether they come from a dotted column, a formula's own navigation, or the combination — so the hops spent reaching a formula reduce the budget left for that formula's own navigation.
|
|
201
|
+
|
|
202
|
+
Still unavailable at SQL time — the column comes back NULL and the **response carries a warning** naming the column and the reason:
|
|
203
|
+
|
|
204
|
+
- Settings references `$[SettingName]`
|
|
205
|
+
- Navigation past the hop cap
|
|
206
|
+
- **Extension-table columns** — a column an extension module added lives in a separate 1:1 ext table needing its own JOIN, and every alias the translator emits is the entity's own base alias. Applies to the hidden FK and to the Reference built on it, on the query's own entity as well as a referenced one
|
|
207
|
+
- **Misauthored nav paths** — a final segment that isn't a column of the referenced entity, or a dot after something that isn't a Reference (`account.account_name.foo`)
|
|
190
208
|
|
|
191
|
-
|
|
209
|
+
Warning wording follows the clause, because the same unresolvable path costs a different thing in each: a SELECT slot goes NULL (*"could not be computed and will be empty"*), an ORDER BY term is dropped so rows come back in a different order (*"Sorting by … was skipped"*), and a WHERE condition is dropped so **more** rows come back than were asked for (*"Filter on … was ignored"*). Untranslatable formula filters are never dropped silently.
|
|
192
210
|
|
|
193
211
|
## Sync vs async formulas
|
|
194
212
|
|
|
@@ -79,8 +79,10 @@ If the source uses non-standard audit column names (e.g. `date_created`, `modifi
|
|
|
79
79
|
|
|
80
80
|
For each FK, create **two columns** following the FK+Reference pattern:
|
|
81
81
|
|
|
82
|
-
1. Hidden FK column: `flags: "EM"
|
|
83
|
-
2. Visible Reference column: `columnType: "R"`, `fieldTypeCd: "lookup"`, `flags
|
|
82
|
+
1. Hidden FK column: `flags: "E"` — `"EM"` only when the source column is `NOT NULL` — matching `dbDatatype`
|
|
83
|
+
2. Visible Reference column: `columnType: "R"`, `fieldTypeCd: "lookup"`, `flags` **matching the FK** (`"VE"` / `"VEM"`), with `link`
|
|
84
|
+
|
|
85
|
+
`M` resolves to `isNullable: false` at install, so carrying it onto an FK the source declares nullable would import the column as `NOT NULL`. `dforge_dbml_import` / `dforge_module_import` take `M` from the source's own nullability.
|
|
84
86
|
|
|
85
87
|
Plus declare the constraint in the entity's `references` block.
|
|
86
88
|
|
|
@@ -42,8 +42,9 @@ For each entity:
|
|
|
42
42
|
### FK+Reference pattern
|
|
43
43
|
|
|
44
44
|
- [ ] Every reference column (`columnType: "R"`) has a paired hidden FK column
|
|
45
|
-
- [ ] The hidden FK column has `flags: "EM"` (not `V`, not `I`)
|
|
46
|
-
- [ ] The reference column has `flags
|
|
45
|
+
- [ ] The hidden FK column has `flags: "E"` — `"EM"` only if the relation is required (not `V`, not `I`)
|
|
46
|
+
- [ ] The reference column has `flags` **matching the FK** (`"VE"` / `"VEM"`), `fieldTypeCd: "lookup"`, and a `link` object
|
|
47
|
+
- [ ] No field declares `M` together with `"isNullable": true` — that combination fails the pack
|
|
47
48
|
- [ ] `link.entity` points to an entity that exists (in this module or a dependency)
|
|
48
49
|
- [ ] `link.thisKey` matches the FK column name on this entity
|
|
49
50
|
- [ ] `link.otherKey` matches the PK column name on the target entity
|
|
@@ -57,8 +57,10 @@ Enough to author inline; load the linked reference for the full detail.
|
|
|
57
57
|
- **A relation is TWO columns + a references entry.** Use
|
|
58
58
|
`dforge_entity_reference_add` — it emits all three parts. → `column-types`
|
|
59
59
|
- **Flags** are letters from `V I E M H` only (never `U`/`S`/`P`): `VEM`
|
|
60
|
-
required+visible, `VE` optional+visible, `V` read-only, `EM` hidden FK
|
|
61
|
-
trait-provided.
|
|
60
|
+
required+visible, `VE` optional+visible, `V` read-only, `E`/`EM` hidden FK
|
|
61
|
+
(optional/required), `I` trait-provided. `M` means required — it resolves to
|
|
62
|
+
`isNullable: false`, so it makes the column `NOT NULL`; don't spend it as
|
|
63
|
+
decoration, and never pair it with `"isNullable": true`. → `flags`
|
|
62
64
|
- **`fieldTypeCd` = UI control; `dbDatatype` = SQL type.** Omit `dbDatatype` on
|
|
63
65
|
plain data columns — it's derived. Set it only for a hidden FK or to override
|
|
64
66
|
size/precision. Common fixes: `number` not `integer`/`float`; `phone` not
|
|
@@ -168,9 +170,14 @@ to fill a sub-step** — a pure CRUD module skips Phase 2 entirely (record it wi
|
|
|
168
170
|
| 2c Jobs | a cron timer fires | `dforge_job_add` |
|
|
169
171
|
| 2d Webhooks | a DB event → outbound POST | `dforge_webhook_add` |
|
|
170
172
|
|
|
171
|
-
**2a Actions.**
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
**2a Actions.** First check the action is an action at all: it must **change**
|
|
174
|
+
something. If the `execute:` block would only read, compute and `info()` the
|
|
175
|
+
answer, build a report, a formula column, or an action that writes a result
|
|
176
|
+
record instead — a printed number is never stored and can't be re-read
|
|
177
|
+
(`dforge://reference/action-dsl`, *When an action is the wrong tool*). Then load
|
|
178
|
+
all four resources in the action row — the wrong-field-access /
|
|
179
|
+
wrong-batch-flag / wrong-`ui/actions.json`-property mistakes only surface when
|
|
180
|
+
you cross-check them. Draft the DSL, run
|
|
174
181
|
**`dforge_action_check({ dslBody, executionMode })`** to catch install failures
|
|
175
182
|
before committing, then `dforge_action_add`. One action per turn.
|
|
176
183
|
|