@dforge-core/dforge-mcp 0.2.17 → 0.2.18
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
CHANGED
|
@@ -4,6 +4,74 @@ 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.18
|
|
8
|
+
|
|
9
|
+
Every counter a module updates — an on-hand quantity, a running balance, a used
|
|
10
|
+
credit — was being written as a literal the script had read a statement earlier.
|
|
11
|
+
Two callers read 10, both write 5, and one decrement is gone with nothing logged.
|
|
12
|
+
`update()` has always accepted `{ inc: n }` for exactly this; nothing here said so,
|
|
13
|
+
so nothing generated it.
|
|
14
|
+
|
|
15
|
+
### Changed — `update()` documents relative values
|
|
16
|
+
|
|
17
|
+
`update('wms.stock', [stock_id], { quantity: { inc: -params[qty] } })` adds `n` to
|
|
18
|
+
the column's **current** value, computed by the database under the row lock the
|
|
19
|
+
`UPDATE` already takes. The DSL reference gains a section under `update()` and the
|
|
20
|
+
`dforge-mcp-author` action-DSL table gains the rule in the `update()` row: use it for
|
|
21
|
+
every counter, balance and quantity — a literal is correct only for a value that does
|
|
22
|
+
not depend on what is already stored.
|
|
23
|
+
|
|
24
|
+
`inc` must be a number and the object's only key; `{ inrc: 5 }` throws rather than
|
|
25
|
+
writing nothing. Not available for extension columns, whose row may not exist yet.
|
|
26
|
+
|
|
27
|
+
Documentation only — `dforge_action_check` never parsed `update()` payloads, so
|
|
28
|
+
`{ inc: n }` validated before this and still does.
|
|
29
|
+
|
|
30
|
+
### Changed — `@dforge-core/metadata` → `0.0.22`, `resources/schemas/` re-vendored
|
|
31
|
+
|
|
32
|
+
**A column code may no longer start with `_`.** `entity.schema.json`'s field pattern
|
|
33
|
+
(and the per-view override pattern) tightened from `^[a-zA-Z_][a-zA-Z0-9_]*$` to
|
|
34
|
+
`^[a-zA-Z][a-zA-Z0-9_]*$`, because the platform reserves the prefix: the record route
|
|
35
|
+
reads `_`-prefixed query parameters as app state (`_layout`) rather than key columns,
|
|
36
|
+
and the print-template context injects `_fmt`, `_raw`, `_color`, `_link`, `_today` and
|
|
37
|
+
`_settings` alongside bare column names. **This can reject metadata that packed
|
|
38
|
+
before** — rename the column; a leading underscore was never addressable end to end.
|
|
39
|
+
|
|
40
|
+
**`constraints` is now a specified object instead of a bare `type: object`.** Each
|
|
41
|
+
entry is `unique` (needs `fields`, and `expression` is refused — a unique constraint
|
|
42
|
+
is its key columns) or `check` (needs `expression`; its optional `fields` does not
|
|
43
|
+
build anything and is reported to the client as the fields to highlight, since an
|
|
44
|
+
identifier inside a string literal is indistinguishable from a column name). `columns`
|
|
45
|
+
survives as a **deprecated** alias for `fields` on a unique constraint — still built,
|
|
46
|
+
still reported, warned about at install. `message` overrides the generated violation
|
|
47
|
+
text and localizes under `entities.<entity>.constraints.<name>.message`. Editors now
|
|
48
|
+
complete this and catch a `check` with no expression at authoring time.
|
|
49
|
+
|
|
50
|
+
Two description corrections in the same bump: a **`reports.json` `entityCd` pointing
|
|
51
|
+
outside the declaring module must be qualified `module.entity`** — `entity_cd` is
|
|
52
|
+
unique per module, not per tenant, and the installer stores the id it resolves once,
|
|
53
|
+
which `report.run` then reads for the life of the row; a bare code that several
|
|
54
|
+
installed modules define is rejected at install unless one of them is this module or a
|
|
55
|
+
declared dependency. And **an `extends` module cannot declare `views`** — a view lists
|
|
56
|
+
the complete visible column set and an extension knows only the columns it adds — which
|
|
57
|
+
`entity.schema.json` now says next to the rule (already shipped in 0.2.16) that a folder
|
|
58
|
+
naming an undeclared view fails the install.
|
|
59
|
+
|
|
60
|
+
Also arriving with `0.0.21`, platform-side and needing nothing from a module:
|
|
61
|
+
`number`, `currency` and `percent` no longer ship `min`/`max` field-type defaults. The
|
|
62
|
+
client merged those under a column's own `params` and validated the result, so every
|
|
63
|
+
plain `number` column was silently capped at 10000 and refused negatives
|
|
64
|
+
([dForge-core#947](https://github.com/iash44/dForge-core/issues/947)) — an odometer, a
|
|
65
|
+
cargo weight, a negative variance. A numeric column is bounded only when its own
|
|
66
|
+
`params` say so, which is unchanged and remains the way to state a real range.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- `@dforge-core/dforge-cli` → `0.2.16`.
|
|
71
|
+
|
|
72
|
+
Nothing else is rejected that was accepted before; apart from the `_`-prefixed column
|
|
73
|
+
codes above, `dforge_module_validate` is unchanged.
|
|
74
|
+
|
|
7
75
|
## 0.2.17
|
|
8
76
|
|
|
9
77
|
A bridge module's `async: false` trigger **can** roll back its host module's write,
|
|
@@ -40,6 +108,17 @@ should expect to see when their trigger fires.
|
|
|
40
108
|
|
|
41
109
|
Nothing is rejected that was accepted before — `dforge_module_validate` is unchanged.
|
|
42
110
|
|
|
111
|
+
### Changed
|
|
112
|
+
|
|
113
|
+
- `@dforge-core/metadata` → `0.0.20`; `resources/schemas/` re-vendored. Besides
|
|
114
|
+
`triggers.schema.json`'s `async` above, that brings three description fixes from
|
|
115
|
+
platform work that landed alongside: `deps.schema.json` on how a target's `columns`
|
|
116
|
+
list treats a column your own `extends` file adds, `manifest.schema.json` warning
|
|
117
|
+
against sharing one `schema` between modules that extend other modules' entities
|
|
118
|
+
(extension columns live in `"{schema}"."{entity}_ext"`, which collides), and
|
|
119
|
+
`menus.schema.json` spelling out that `folders` takes flat folder codes
|
|
120
|
+
(`logistics_all_trips`), not paths.
|
|
121
|
+
|
|
43
122
|
## 0.2.16
|
|
44
123
|
|
|
45
124
|
Entity views — the platform's **column-level security** — became reachable from a
|
package/dist/server.js
CHANGED
|
@@ -31002,7 +31002,7 @@ var import_node_crypto = require("crypto");
|
|
|
31002
31002
|
var fs2 = __toESM(require("fs"));
|
|
31003
31003
|
var path2 = __toESM(require("path"));
|
|
31004
31004
|
|
|
31005
|
-
// node_modules/.pnpm/@dforge-core+dforge-cli@0.2.
|
|
31005
|
+
// node_modules/.pnpm/@dforge-core+dforge-cli@0.2.16/node_modules/@dforge-core/dforge-cli/dist/lib.mjs
|
|
31006
31006
|
function buildManifest(opts, moduleId) {
|
|
31007
31007
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
31008
31008
|
const manifest = {
|
|
@@ -31214,7 +31214,7 @@ function buildZedSettings() {
|
|
|
31214
31214
|
var fs = __toESM(require("fs"));
|
|
31215
31215
|
var path = __toESM(require("path"));
|
|
31216
31216
|
|
|
31217
|
-
// node_modules/.pnpm/@dforge-core+metadata@0.0.
|
|
31217
|
+
// node_modules/.pnpm/@dforge-core+metadata@0.0.22/node_modules/@dforge-core/metadata/dist/index.js
|
|
31218
31218
|
var AggType = {
|
|
31219
31219
|
Sum: "sum",
|
|
31220
31220
|
Count: "count",
|
|
@@ -31231,9 +31231,9 @@ var fieldTypes = [
|
|
|
31231
31231
|
{ cd: "phone", name: "Phone", columnType: "D", baseDatatypeCd: "string", supportedAgg: null, maxLen: null, precision: null, useStringInFilter: true },
|
|
31232
31232
|
{ cd: "url", name: "URL", columnType: "D", baseDatatypeCd: "string", supportedAgg: null, maxLen: null, precision: null, useStringInFilter: true },
|
|
31233
31233
|
{ cd: "textarea", name: "Multiline Text", columnType: "D", baseDatatypeCd: "string", supportedAgg: null, maxLen: null, precision: null, useStringInFilter: true },
|
|
31234
|
-
{ cd: "number", name: "Number", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: null, useStringInFilter: false
|
|
31235
|
-
{ cd: "currency", name: "Currency", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: 2, useStringInFilter: false
|
|
31236
|
-
{ cd: "percent", name: "Percent", columnType: "D", baseDatatypeCd: "number", supportedAgg: "CAM", maxLen: null, precision: 2, useStringInFilter: false
|
|
31234
|
+
{ cd: "number", name: "Number", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: null, useStringInFilter: false },
|
|
31235
|
+
{ cd: "currency", name: "Currency", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: 2, useStringInFilter: false },
|
|
31236
|
+
{ cd: "percent", name: "Percent", columnType: "D", baseDatatypeCd: "number", supportedAgg: "CAM", maxLen: null, precision: 2, useStringInFilter: false },
|
|
31237
31237
|
{ cd: "checkbox", name: "Checkbox", columnType: "D", baseDatatypeCd: "bool", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: false },
|
|
31238
31238
|
{ cd: "date", name: "Date", columnType: "D", baseDatatypeCd: "date", supportedAgg: "CM", maxLen: null, precision: null, useStringInFilter: false },
|
|
31239
31239
|
{ cd: "datetime", name: "Date & Time", columnType: "D", baseDatatypeCd: "timestamp", supportedAgg: "CM", maxLen: null, precision: null, useStringInFilter: false },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dforge-core/dforge-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"description": "MCP server for dForge module authoring. Exposes 34 scaffold/patch/validate/pack/install tools and schema resources so AI agents (Claude Code, Cursor, Zed) can create and ship dForge modules.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/iash44/dForge-core",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"install-skills": "node scripts/install-skills.mjs"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@dforge-core/dforge-cli": "^0.2.
|
|
39
|
-
"@dforge-core/metadata": "^0.0.
|
|
38
|
+
"@dforge-core/dforge-cli": "^0.2.16",
|
|
39
|
+
"@dforge-core/metadata": "^0.0.22",
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
41
41
|
"zod": "^4.4.3"
|
|
42
42
|
},
|
|
@@ -264,6 +264,20 @@ coerced to the PK storage type (snowflake-as-string binds `int8`). A null key
|
|
|
264
264
|
update('comm.message', msg.message_id, { status: 'sent', sent_at: now() })
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
+
**Relative changes — `{ inc: n }`.** A field value may be `{ inc: n }` instead of a
|
|
268
|
+
literal, which adds `n` to the column's **current** value. Use it for every counter,
|
|
269
|
+
balance or on-hand quantity:
|
|
270
|
+
|
|
271
|
+
```dsl
|
|
272
|
+
// The DATABASE subtracts, under the row lock the UPDATE already takes
|
|
273
|
+
update('wms.stock', [stock_id], { quantity: { inc: -params[qty] } })
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
A literal can only carry a number the script read *before* the statement ran, so two
|
|
277
|
+
callers that both read 10 both write 5 and one decrement is silently lost. `inc` must
|
|
278
|
+
be a number and the object's only key — `{ inrc: 5 }` throws rather than writing
|
|
279
|
+
nothing. Not available for extension columns (their row may not exist yet).
|
|
280
|
+
|
|
267
281
|
#### `delete(entityCd, key) → int`
|
|
268
282
|
Delete the row(s) matched by `key`; returns rows affected. Same key shapes and
|
|
269
283
|
rules as `update()`.
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
},
|
|
50
50
|
"fields": {
|
|
51
51
|
"type": "object",
|
|
52
|
-
"description": "Column definitions keyed by column code. Column codes must start with a
|
|
52
|
+
"description": "Column definitions keyed by column code. Column codes must start with a LETTER and contain only letters, digits, and underscores. A leading `_` is reserved by the platform — the record route treats `_`-prefixed query parameters as app state (`_layout`) rather than key columns, and the print template context injects `_fmt`, `_raw`, `_color`, `_link`, `_today` and `_settings` alongside bare column names. A leading `-` is reserved for the sort descending prefix in `view_json.order`.",
|
|
53
53
|
"propertyNames": {
|
|
54
|
-
"pattern": "^[a-zA-
|
|
54
|
+
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$"
|
|
55
55
|
},
|
|
56
56
|
"additionalProperties": {
|
|
57
57
|
"$ref": "#/$defs/field"
|
|
@@ -59,7 +59,51 @@
|
|
|
59
59
|
},
|
|
60
60
|
"constraints": {
|
|
61
61
|
"type": "object",
|
|
62
|
-
"description": "Database constraints
|
|
62
|
+
"description": "Database constraints, keyed by constraint name. Each becomes a real Postgres constraint; a violation is reported to the client under UNIQUE_VIOLATION or CHECK_VIOLATION with the constraint name, the owning entity and its declared fields.",
|
|
63
|
+
"additionalProperties": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"required": ["type"],
|
|
66
|
+
"allOf": [
|
|
67
|
+
{
|
|
68
|
+
"if": { "required": ["type"], "properties": { "type": { "const": "unique" } } },
|
|
69
|
+
"then": {
|
|
70
|
+
"anyOf": [{ "required": ["fields"] }, { "required": ["columns"] }],
|
|
71
|
+
"properties": { "expression": false }
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"if": { "required": ["type"], "properties": { "type": { "const": "check" } } },
|
|
76
|
+
"then": { "required": ["expression"] }
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"properties": {
|
|
80
|
+
"type": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"enum": ["unique", "check"],
|
|
83
|
+
"description": "Constraint kind"
|
|
84
|
+
},
|
|
85
|
+
"fields": {
|
|
86
|
+
"type": "array",
|
|
87
|
+
"items": { "type": "string" },
|
|
88
|
+
"description": "Columns the constraint governs. Required for 'unique' - it IS the key. Optional for 'check', where it does not build the constraint but is reported to the client as the fields to highlight - the expression is never parsed for this, because an identifier inside a string literal is indistinguishable from a column name."
|
|
89
|
+
},
|
|
90
|
+
"columns": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"items": { "type": "string" },
|
|
93
|
+
"deprecated": true,
|
|
94
|
+
"deprecationMessage": "Deprecated: use 'fields'. The constraint is still created and its violation still reports the columns - the alias is read as a fallback on a unique constraint - but 'fields' is the documented spelling, the only one a check constraint reads, and the only one that will keep working. Module install warns about this.",
|
|
95
|
+
"description": "DEPRECATED alias for 'fields' on a unique constraint - use 'fields' instead. Accepted when building the constraint and read as a fallback when reporting a violation."
|
|
96
|
+
},
|
|
97
|
+
"expression": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "SQL boolean expression the row must satisfy. Required for 'check', and not valid on 'unique' - a unique constraint is defined by its key columns, not an expression."
|
|
100
|
+
},
|
|
101
|
+
"message": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": "Violation message shown to the user; overrides the generated default. Localizable via translations/<locale>.json under entities.<entity>.constraints.<name>.message"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
63
107
|
},
|
|
64
108
|
"references": {
|
|
65
109
|
"type": "object",
|
|
@@ -98,7 +142,7 @@
|
|
|
98
142
|
},
|
|
99
143
|
"views": {
|
|
100
144
|
"type": "object",
|
|
101
|
-
"description": "Entity views keyed by view name — column-level security. Unrelated to 'isView'/'viewSql' (a SQL-view-backed entity) and to ui/data_views.json (grids, kanban, calendars). A folder binds one view per entity via ui/folders.json (entities.<code>.viewName); users working in that folder see ONLY the columns the view lists. Entities without views
|
|
145
|
+
"description": "Entity views keyed by view name — column-level security. Unrelated to 'isView'/'viewSql' (a SQL-view-backed entity) and to ui/data_views.json (grids, kanban, calendars). A folder binds one view per entity via ui/folders.json (entities.<code>.viewName); users working in that folder see ONLY the columns the view lists. Entities without views show the entity's full column set; a folder naming a view that isn't declared here FAILS the install, since resolving it to nothing would show every column \u2014 the opposite of what naming a view asks for. A view can only be declared by the entity's own module: an extension ('extends') declaring 'views' is rejected, because a view lists the complete column set and an extension knows only the columns it adds.",
|
|
102
146
|
"propertyNames": {
|
|
103
147
|
"pattern": "^[a-zA-Z_][a-zA-Z0-9_-]*$",
|
|
104
148
|
"maxLength": 100
|
|
@@ -120,7 +164,7 @@
|
|
|
120
164
|
"description": "Column code -> per-view overrides. Columns absent from this map are hidden in a folder bound to this view. Use {} (or null) to expose a column with no overrides. Must include the entity's primary key column(s) — records are addressed by them; hide the key from the UI by omitting 'V' from its flags instead.",
|
|
121
165
|
"minProperties": 1,
|
|
122
166
|
"propertyNames": {
|
|
123
|
-
"pattern": "^[a-zA-
|
|
167
|
+
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$"
|
|
124
168
|
},
|
|
125
169
|
"additionalProperties": {
|
|
126
170
|
"$ref": "#/$defs/entityViewColumn"
|
|
@@ -273,7 +273,7 @@
|
|
|
273
273
|
"properties": {
|
|
274
274
|
"entityCd": {
|
|
275
275
|
"type": "string",
|
|
276
|
-
"description": "Source entity code"
|
|
276
|
+
"description": "Source entity code. Qualify it as `module.entity` for anything outside the declaring module: entity_cd is unique per module, not per tenant, and the installer stores the id it resolves once — report.run reads that id for the life of the row. A bare code that several installed modules define is rejected at install unless one of them is this module or a declared dependency."
|
|
277
277
|
},
|
|
278
278
|
"columns": {
|
|
279
279
|
"type": "array",
|
|
@@ -226,7 +226,7 @@ var note = params[note] // no quotes around param name in this DSL
|
|
|
226
226
|
|---|---|---|
|
|
227
227
|
| `insert('entity', { field: value, … })` | Inserted row object | Insert a new record. Returns the full row including auto-filled columns (PK, audit, number sequences). Access returned fields with `result.field_name`. |
|
|
228
228
|
| `select('entity', opts?)` | Array of row objects | Structured multi-row read — see "select()" below. `opts` = `{columns, filter, orderBy, limit, offset}`, all optional. Prefer over `query()` for row-shaped reads. |
|
|
229
|
-
| `update('entity', key, { field: value, … })` | Rows affected (number) | Update the row(s) matched by `key`. Refreshes update-phase server defaults (audit `last_updated` / `last_updated_by`) and coerces values like `insert()`. |
|
|
229
|
+
| `update('entity', key, { field: value, … })` | Rows affected (number) | Update the row(s) matched by `key`. Refreshes update-phase server defaults (audit `last_updated` / `last_updated_by`) and coerces values like `insert()`. A value may be `{ inc: n }` instead of a literal — adds `n` to the column's CURRENT value, so concurrent callers accumulate instead of overwriting. Use it for every counter / balance / quantity. |
|
|
230
230
|
| `delete('entity', key)` | Rows affected (number) | Delete the row(s) matched by `key`. |
|
|
231
231
|
| `query('SQL', { arg: value })` | Array of row objects | Execute parameterized SQL. Use `@argName` placeholders. Schema-qualified table names (`crm.contact`). Access row fields with `row.field_name` or `row['field_name']`. **Escape hatch** — prefer `select()`/`update()`/`delete()` when the structured shape fits. |
|
|
232
232
|
| `getRecord('entity', key)` | Row object | Fetch a single record by key. Access fields with `rec.field_name` (or `rec.get('field_name')`). **Throws** a localized "not found" error if absent. |
|