@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
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,193 @@ All notable changes to `@dforge-core/dforge-mcp`. This project uses semver-ish
|
|
|
4
4
|
`0.1.0-rc.N` pre-release tags; the published version is set at publish time via
|
|
5
5
|
the release workflow, so committed `package.json` versions are placeholders.
|
|
6
6
|
|
|
7
|
+
## 0.2.2
|
|
8
|
+
|
|
9
|
+
Mirrors the platform change that gives the `M` column flag its meaning — in the
|
|
10
|
+
docs, in the schemas, and in the tools that emit flags — and catches up the
|
|
11
|
+
formula and `entityLink()` references with the platform work since 0.2.1.
|
|
12
|
+
|
|
13
|
+
### Fixed — the relation tools emitted `NOT NULL` for every optional relation
|
|
14
|
+
|
|
15
|
+
The `M` change turned an inert flag into a physical constraint, which made three
|
|
16
|
+
places that hard-coded it actively wrong. All three now take `M` from whether the
|
|
17
|
+
relation is actually required, and put it on **both** halves in step:
|
|
18
|
+
|
|
19
|
+
- **`dforge_entity_reference_add`** emitted the hidden FK as `flags: "EM"`
|
|
20
|
+
unconditionally, whatever `required` said. Post-change that made every optional
|
|
21
|
+
relation's FK column `NOT NULL` while its own Reference half said `"VE"` — a pair
|
|
22
|
+
that contradicts itself. It now emits FK `"EM"` + Reference `"VEM"` when
|
|
23
|
+
`required`, and FK `"E"` + Reference `"VE"` when not. Reusing a half-built FK
|
|
24
|
+
normalizes to the same shape, and drops a lingering `"isNullable": true` when the
|
|
25
|
+
relation is required rather than emitting a pair the platform rejects at pack.
|
|
26
|
+
- **`dforge_dbml_import` / `dforge_module_import`** did the same for every imported
|
|
27
|
+
FK. They now read the source column's nullability — a DBML `[not null]` on the FK
|
|
28
|
+
is carried onto the reference (the FK column itself is dropped from `columns`, so
|
|
29
|
+
it was being lost) and `required` is accepted explicitly on a table-spec reference.
|
|
30
|
+
- **`isHiddenFk()`** required `M` to recognize the hidden half of a pair, so an
|
|
31
|
+
optional FK (`"E"`) became invisible to every rule keyed off it. `M` is not part
|
|
32
|
+
of the shape — it marks the relation required.
|
|
33
|
+
- **Set columns** are emitted `"VE"`, not `"VEM"`: `M` is inert on a virtual column,
|
|
34
|
+
but it reads as "this grid must be non-empty", which nothing enforces.
|
|
35
|
+
|
|
36
|
+
### Added — the validator catches the `M` / `isNullable` contradiction
|
|
37
|
+
|
|
38
|
+
`dforge_module_validate` (and the authoring-time field schema) now error on a field
|
|
39
|
+
declaring `M` together with `"isNullable": true`, naming the field. The platform
|
|
40
|
+
rejects this at pack time; catching it here says which column while you can still
|
|
41
|
+
see it. On a virtual column the message says so instead of offering both fixes.
|
|
42
|
+
|
|
43
|
+
### Changed — the `M` column flag now means "required" (platform mirror)
|
|
44
|
+
|
|
45
|
+
- **`M` resolves to `isNullable: false` at install.** The flag was documented as
|
|
46
|
+
"mandatory" but was inert — nothing on the server or the client read it, so a
|
|
47
|
+
column declared `"flags": "VEM"` rendered without a red asterisk and saved
|
|
48
|
+
empty. It is now folded into `isNullable`, the platform's single notion of
|
|
49
|
+
required, and everything downstream keys off that: `NOT NULL` in generated
|
|
50
|
+
DDL, the `data.insert` required-column check, the client asterisk.
|
|
51
|
+
- **Declaring `M` together with `"isNullable": true` is now a hard error** at
|
|
52
|
+
pack time, naming the entity and every offending field — it is a contradiction
|
|
53
|
+
only the author can resolve. **This repo shipped exactly that combination** in
|
|
54
|
+
the extension example in `resources/docs/conventions.md` (`customer_id`, `"flags":
|
|
55
|
+
"EM"` with `"isNullable": true`), so a module authored from that pattern could
|
|
56
|
+
not pack. Corrected to `"flags": "E"`, matching the real `crm-fin` module.
|
|
57
|
+
- **The FK+Reference rule no longer prescribes `M` by default.** A hidden FK is
|
|
58
|
+
`flags: "E"`, or `"EM"` when the relationship is genuinely required; omitting
|
|
59
|
+
both leaves the column nullable, which is the default — so the examples no
|
|
60
|
+
longer spell out `"isNullable": true` either. Keep the visible Reference in
|
|
61
|
+
step (`VE` / `VEM`): `M` is inert on a virtual column, so `VEM` over an
|
|
62
|
+
optional FK changes nothing but reads as a required field to the next author,
|
|
63
|
+
which is precisely how the broken example above went unnoticed.
|
|
64
|
+
- **An upgrade now reconciles nullability in both directions.** Previously
|
|
65
|
+
neither direction reached an existing table: `ADD COLUMN IF NOT EXISTS` no-ops
|
|
66
|
+
on a column that is already there, and the only nullability statement emitted
|
|
67
|
+
was `DROP NOT NULL`, and that only for an *explicit* `isNullable: true`. So
|
|
68
|
+
adding `M` to a shipped field silently did nothing on upgraded tenants (it
|
|
69
|
+
worked on a fresh install, which goes through `CREATE TABLE`), and merely
|
|
70
|
+
deleting `M` left the constraint standing, failing later inserts with a raw
|
|
71
|
+
Postgres 23502.
|
|
72
|
+
- **Authoring consequence:** adding `M` to a field that existing tenant rows
|
|
73
|
+
leave empty now **aborts the upgrade**, rolled back, naming the column and its
|
|
74
|
+
NULL row count — rather than committing a tenant whose API enforces a rule its
|
|
75
|
+
data already breaks. Give the column a `params.serverDefault` if the install
|
|
76
|
+
should backfill it. Removing `M` is always safe.
|
|
77
|
+
- **`params.serverDefault` now works on a required column.** The platform's
|
|
78
|
+
required-for-insert check excluded `formula` and `numberSequence` but not
|
|
79
|
+
`serverDefault`, so an insert omitting such a column was rejected as missing
|
|
80
|
+
even though the INSERT would have supplied the value. An `on: "update"`
|
|
81
|
+
default stays required — it does not fire on insert.
|
|
82
|
+
- **Also corrected in `conventions.md`:** the hidden-FK examples carried
|
|
83
|
+
`"fieldTypeCd": "hidden"`, which makes install warn that a `cuid` column
|
|
84
|
+
(group *number*) is bound to a *text* field type and can misbind filter/DML
|
|
85
|
+
values at runtime (`operator does not exist: bigint = text`). A hidden FK is
|
|
86
|
+
`dbDatatype: "cuid"` + `flags` — no `fieldTypeCd`.
|
|
87
|
+
|
|
88
|
+
### Changed — formula navigation at SQL time is no longer single-hop
|
|
89
|
+
|
|
90
|
+
`formulas.md` and `column-types.md` still described the report/query translator as
|
|
91
|
+
single-hop-only, and told authors to keep report-bound formulas to one hop. That
|
|
92
|
+
stopped being true upstream:
|
|
93
|
+
|
|
94
|
+
- **Multi-hop `[a].[b].[c]` translates**, one `LEFT JOIN` per hop, each with the
|
|
95
|
+
same soft-deny rights check as a reference display column. A nav path may land on
|
|
96
|
+
a *formula* column of the referenced entity, whose own hops are joined off that
|
|
97
|
+
alias — which is what makes a two-level formula reachable through a reference.
|
|
98
|
+
- **Hops are counted from the query's root** and capped at `MaxNavigationDepth`, so
|
|
99
|
+
the hops spent reaching a formula reduce the budget left for its own navigation.
|
|
100
|
+
- **What is still unsupported changed shape:** `$[Setting]` references, paths past
|
|
101
|
+
the cap, **extension-table columns** (they live in a separate 1:1 table needing
|
|
102
|
+
its own JOIN), and misauthored paths. All come back NULL with a response warning.
|
|
103
|
+
- **Warning wording follows the clause** — a SELECT slot goes empty, an ORDER BY
|
|
104
|
+
term is dropped so rows come back in a different order, a WHERE condition is
|
|
105
|
+
dropped so *more* rows come back than were asked for. Documented, because the
|
|
106
|
+
third one is the only failure that silently widens a result set.
|
|
107
|
+
- **Comparison is null-safe and two-valued** in every context: `null = null` is
|
|
108
|
+
true, `[x] != null` is true for any non-null `x`, and SQL-time `=`/`!=` translate
|
|
109
|
+
to `IS NOT DISTINCT FROM` / `IS DISTINCT FROM` so a card and a report agree.
|
|
110
|
+
- Corrected in both files: nav resolution walks N:1 references only, **never a 1:N
|
|
111
|
+
set**, however many hops it is given — the reason `SUM([set].[field])` in an `F`
|
|
112
|
+
column renders empty. That was previously attributed to the single-hop limit.
|
|
113
|
+
|
|
114
|
+
### Changed — `entityLink()` stores the entity id and captions from `toString`
|
|
115
|
+
|
|
116
|
+
`entity_cd` is only unique per module, so storing the bare code meant a link built
|
|
117
|
+
as `entityLink('fin.invoice', …)` re-resolved at click time against the *reader's*
|
|
118
|
+
folder module and could land on another module's `invoice`. It now stores the
|
|
119
|
+
resolved entity id. The display caption comes from the entity's `toString`
|
|
120
|
+
evaluated against the record (an explicit `description` still wins) — previously
|
|
121
|
+
the entity's own description, which names the table, not the record. Documented in
|
|
122
|
+
`action-dsl.md`, `field-types.md` and the `dforge://reference/dsl` resource, with
|
|
123
|
+
the reminder to pass a qualified code.
|
|
124
|
+
|
|
125
|
+
### Changed — dependencies
|
|
126
|
+
|
|
127
|
+
- **`@dforge-core/metadata` `^0.0.10` → `^0.0.12`**, and the vendored schemas
|
|
128
|
+
re-generated from it. `entity.schema.json` and `triggers.schema.json` were stale:
|
|
129
|
+
they still described `M` as plain "Mandatory" with no mention of `isNullable`, and
|
|
130
|
+
omitted that DSL `insert()`/`update()`/`delete()`/`query()` writes to *other*
|
|
131
|
+
records raise no trigger events.
|
|
132
|
+
- **`@dforge-core/dforge-cli` `^0.2.7` → `^0.2.10`.**
|
|
133
|
+
|
|
134
|
+
## 0.2.1
|
|
135
|
+
|
|
136
|
+
Adds the column-domain schema that `dforge_module_inspect` already reported on,
|
|
137
|
+
and corrects three areas where the schemas and reference docs described
|
|
138
|
+
behavior the platform doesn't have.
|
|
139
|
+
|
|
140
|
+
### Added — column domains
|
|
141
|
+
|
|
142
|
+
- **`resources/schemas/domains.schema.json`** — `domains.json` had no schema, so
|
|
143
|
+
the one file that defines reusable semantic types was the only module file
|
|
144
|
+
authored blind. A domain bundles a base datatype, a control, sizing and a
|
|
145
|
+
shared option list under one code. Structural fields (`dbDatatype`,
|
|
146
|
+
`baseDatatypeCd`, `fieldTypeCd`, `maxLen`, `precision`) are materialized onto
|
|
147
|
+
each consuming column at install; `params` — most importantly `options` —
|
|
148
|
+
stays on the domain and resolves at metadata-load time, which is what lets a
|
|
149
|
+
shared list be authored and translated exactly once.
|
|
150
|
+
- **`entity.schema.json` gains `domain`** plus a *Domain-backed column* branch in
|
|
151
|
+
the column `oneOf`. Referencing a domain and restating what it owns is
|
|
152
|
+
rejected at install, so the schema now refuses the same combination rather
|
|
153
|
+
than letting it through to a failed install.
|
|
154
|
+
|
|
155
|
+
### Fixed — trigger semantics were documented wrong
|
|
156
|
+
|
|
157
|
+
`triggers.schema.json` and `dforge_trigger_add` both described an invocation
|
|
158
|
+
contract the runtime doesn't implement, and agents wrote actions against it:
|
|
159
|
+
|
|
160
|
+
- **There is no injected `record_id` param.** The trigger binds the affected
|
|
161
|
+
record to the action as *record context* — the action reads it with `[field]`
|
|
162
|
+
and its PK with `[pk_column]`, exactly like a UI-invoked action, with
|
|
163
|
+
pre-change values as `old[field]` on update/delete/status_change. `params` is
|
|
164
|
+
static-only: the literal values written in the trigger, nothing else.
|
|
165
|
+
- **The action's `entityCode` must equal the trigger's `entity`**, and its
|
|
166
|
+
`executionMode` must not be `batch` — both enforced at pack time. The old
|
|
167
|
+
description said the opposite, warning the action must *not* rely on the
|
|
168
|
+
triggering record.
|
|
169
|
+
- **`condition` rejects reference navigation.** The event-time evaluator reads
|
|
170
|
+
only columns of the changed row, so `[ref].[target]` resolves to null and the
|
|
171
|
+
trigger misfires. Navigate inside the action, or denormalise onto the watched
|
|
172
|
+
entity.
|
|
173
|
+
- **`async` defaults to `true`**, matching the platform. The tool defaulted it to
|
|
174
|
+
`false`, so every trigger added without an explicit `async` ran synchronously
|
|
175
|
+
inside the user's transaction — where a failing action rolls back their write.
|
|
176
|
+
|
|
177
|
+
### Fixed — actions are not reliably atomic
|
|
178
|
+
|
|
179
|
+
- **`isTransacted` does not open a transaction.** It only decides whether the
|
|
180
|
+
first failing record aborts the run or the loop continues. Whether writes made
|
|
181
|
+
before a failure survive depends on the execution path: `auditHistory` on the
|
|
182
|
+
entity, `single` vs `batch`, sync vs background. `action-dsl.md` now tabulates
|
|
183
|
+
all six combinations, and `dsl-reference.md` no longer claims `error()` rolls
|
|
184
|
+
back the whole transaction. This is a platform defect rather than a contract —
|
|
185
|
+
actions are intended to be atomic — so both documents say to validate before
|
|
186
|
+
writing anything, and not to design around either outcome.
|
|
187
|
+
- **`query()` runs on a pooled, shared connection**, which nothing checks
|
|
188
|
+
statically and neither document mentioned: `SET LOCAL` not `SET`,
|
|
189
|
+
`pg_advisory_xact_lock()` not `pg_advisory_lock()`, `ON COMMIT DROP` on temp
|
|
190
|
+
tables.
|
|
191
|
+
- Documented `old[fieldName]`, and the single-hop limit on `[ref].[target]` —
|
|
192
|
+
`[a].[b].[c]` is a compile error, not a slow path.
|
|
193
|
+
|
|
7
194
|
## 0.2.0
|
|
8
195
|
|
|
9
196
|
Tools go from 27 → 34; the single wizard skill splits into a router plus three
|