@dforge-core/dforge-mcp 0.1.7 → 0.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dforge-core/dforge-mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "MCP server for dForge module authoring. Exposes scaffold/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",
@@ -32,14 +32,14 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@dforge-core/dforge-cli": "^0.2.7",
35
- "@dforge-core/metadata": "^0.0.5",
35
+ "@dforge-core/metadata": "^0.0.8",
36
36
  "@modelcontextprotocol/sdk": "^1.29.0",
37
37
  "zod": "^4.4.3"
38
38
  },
39
39
  "devDependencies": {
40
- "@types/node": "^22.10.2",
40
+ "@types/node": "^22.20.1",
41
41
  "tsup": "^8.5.1",
42
42
  "typescript": "^6.0.3",
43
- "vitest": "^3.2.0"
43
+ "vitest": "^3.2.7"
44
44
  }
45
45
  }
@@ -168,15 +168,28 @@ var po = insert('purchase_order', { supplier_id: params[supplier].supplier_id })
168
168
  insert('po_line', { po_id: po.purchase_order_id, product_id: [product_id], qty: 1 })
169
169
  ```
170
170
 
171
- #### `getRecord(entityCd, id) → record | null`
172
- Fetch a single record by PK. Returns null if not found.
171
+ #### `getRecord(entityCd, key) → record`
172
+ Fetch a single record by key. **Throws** a localized "not found" error if no row
173
+ matches — the safe default for a lookup that should always resolve (e.g. an FK you
174
+ just read). Fields are readable by dot notation (`rec.name`) or `rec.get('name')`.
175
+ `key` is a scalar PK, or an object for a compound key: `getRecord('gl.tag', { tag_group: 'REGION', tag_code: 'EU' })`.
176
+ The result is a **read-only** snapshot — writing to it (`rec.set(...)`) throws; use
177
+ `update()` to persist. A null key value can't match, so it counts as not-found.
173
178
 
174
179
  ```dsl
175
180
  var rec = getRecord('crm.customer', [customer_id])
176
- if (rec == null) { error('Customer not found') }
177
181
  info('Customer: ' + rec.name + ' — credit limit $' + rec.credit_limit)
178
182
  ```
179
183
 
184
+ #### `getRecordOrNull(entityCd, key) → record | null`
185
+ Same as `getRecord`, but returns `null` instead of throwing when the row is absent —
186
+ use it when a missing record is an expected outcome (optional lookup, upsert probe).
187
+
188
+ ```dsl
189
+ var existing = getRecordOrNull('crm.customer', [customer_id])
190
+ if (existing == null) { insert('crm.customer', { customer_id: [customer_id], name: [name] }) }
191
+ ```
192
+
180
193
  #### `callProc(name, args?) → record`
181
194
  Call a stored procedure with named args. Returns the proc's result row (or rows if it RETURNS TABLE).
182
195
 
@@ -472,6 +472,16 @@
472
472
  "field": { "type": "string" }
473
473
  },
474
474
  "additionalProperties": false
475
+ },
476
+ "onDelete": {
477
+ "type": "string",
478
+ "enum": ["cascade", "setNull", "restrict", "noAction"],
479
+ "description": "FK ON DELETE behaviour. Default (omitted) = noAction (plain FK). 'cascade' deletes children with the parent; 'setNull' nulls the FK column (must be nullable)."
480
+ },
481
+ "onUpdate": {
482
+ "type": "string",
483
+ "enum": ["cascade", "setNull", "restrict", "noAction"],
484
+ "description": "FK ON UPDATE behaviour. Default (omitted) = noAction. Fires when a parent's key value changes — a no-op for immutable cuid PKs, but useful for entities keyed on a natural/mutable PK."
475
485
  }
476
486
  },
477
487
  "additionalProperties": false
@@ -50,7 +50,7 @@
50
50
  "async": {
51
51
  "type": "boolean",
52
52
  "default": false,
53
- "description": "When true, the action runs in the background — the triggering transaction commits before the action starts. Recommended for slow actions (emails, external API calls). When false, the action runs in the same transaction; failures roll back the original DB change."
53
+ "description": "When true, the action runs in the background — the triggering transaction commits before the action starts. Recommended for slow actions (emails, external API calls); background failures are logged, not surfaced to the caller. When false, the action runs synchronously inside the same transaction as the triggering insert/update/delete: if the action fails (e.g. calls error()), the whole mutation rolls back and the error is returned to the caller. Keep async:false actions fast — they run on the request's transaction."
54
54
  }
55
55
  },
56
56
  "additionalProperties": false
@@ -107,7 +107,8 @@ var note = params[note] // no quotes around param name in this DSL
107
107
  |---|---|---|
108
108
  | `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`. |
109
109
  | `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']`. |
110
- | `getRecord('entity', id)` | Row object | Fetch a single record by primary key. |
110
+ | `getRecord('entity', key)` | Row object | Fetch a single record by key (qualified `module.entity` supported). `key` is a scalar PK or an object for a compound key (`{ col_a: 1, col_b: 'x' }`). Access fields with `rec.field_name` (or `rec.get('field_name')`). **Throws** a localized "not found" error if absent. |
111
+ | `getRecordOrNull('entity', key)` | Row object or `null` | Like `getRecord`, but returns `null` instead of throwing when the row is absent — use for an expected-absence lookup, guarded with `if (rec == null)`. |
111
112
  | `preloadRef('fk_column')` | Row object | Load the referenced record for a FK column. Access its fields with `ref.get('field')`. |
112
113
 
113
114
  ### Messaging
@@ -58,6 +58,28 @@ Lives in: `ui/data_views.json`
58
58
  }
59
59
  ```
60
60
 
61
+ ## Critical rule — the entity needs a visible column
62
+
63
+ A grid-style view can only render an entity that has at least one **visible scalar column** — a field whose `flags` include `V` and whose `columnType` is **not** a set (`S`). Column visibility is entity-driven (the field's flags), **not** view-driven: the `columns` array inside `dataSources[]` is layout-only (reorder/pin/width among already-visible fields) — it can't make a non-`V` field appear.
64
+
65
+ If none of an entity's fields are visible (or only its set/child-collection columns are), the view renders the runtime empty state *"No visible columns configured for this entity."* — a broken screen the user only hits when they open the menu item. `dforge_module_validate` and `dforge_module_pack` now **reject** this before install.
66
+
67
+ ```jsonc
68
+ // WRONG — grid over an entity whose fields are all hidden (no "V")
69
+ "fields": {
70
+ "code": { "fieldTypeCd": "text", "flags": "EM" }, // E=editable, M=… but no V
71
+ "lines": { "columnType": "S", "flags": "VEM" } // visible, but a SET — doesn't count for a grid
72
+ }
73
+ // RIGHT — at least one visible scalar field
74
+ "fields": {
75
+ "code": { "fieldTypeCd": "text", "flags": "VEM" } // V present, not a set → satisfies the grid
76
+ }
77
+ ```
78
+
79
+ This applies to every column-rendering view type (grid, list, kanban, calendar, gallery, tree-grid, master-detail, and the default). The column-agnostic types — `diagram`, `matrix`, `library` — draw from `viewConfig`/relationships instead, so they're **exempt** from this rule.
80
+
81
+ See [flags.md](flags.md) for the flag string, [column-types.md](column-types.md) for `columnType` (`R`/`S`/`F`).
82
+
61
83
  ## View types
62
84
 
63
85
  | `viewType` | Description | `viewConfig` | When to use |
@@ -243,3 +265,4 @@ Most views only have one source. Order is view-level and applies to the primary
243
265
  - Object-array order like `[{ column_cd: "...", direction: "asc" }]` — **wrong shape for data views**. Use `string[]` with leading `-` for descending.
244
266
  - `viewType` is **optional** — omitted or unknown values fall back to `grid` at runtime. Set it explicitly for any non-grid view (`kanban`, `calendar`, `matrix`, …); a `matrix` view also **requires** a `viewConfig` with `rowAxis`/`colAxis`/`cell`.
245
267
  - Inventing view types like `"spreadsheet"` or `"table"` — **wrong**. Use `grid`.
268
+ - A grid-style view over an entity with **no visible scalar column** (no field flagged `V`, or only set columns) — **rejected** at validate/pack. Mark at least one non-set field visible. See the [critical rule above](#critical-rule--the-entity-needs-a-visible-column). `diagram`/`matrix`/`library` are exempt.
@@ -53,10 +53,12 @@ Source of truth: `server/database/system-modules/metadata/seed-data/field_types.
53
53
 
54
54
  ## Binary / file
55
55
 
56
- | `fieldTypeCd` | `baseDatatypeCd` | Description | Params |
57
- |---|---|---|---|
58
- | `file` | `binary` | Arbitrary file upload. | `accept`, `maxSize` |
59
- | `image` | `binary` | Image upload with preview. | `maxSize`, `dimensions` |
56
+ > **Stored as `jsonb`, not `bytea`.** Despite the `binary` base datatype, a `file`/`image` column holds a JSON metadata reference (`{ storagePath, fileName, … }`) — the bytes live in file storage. The field tools derive `jsonb`; do **not** set `dbDatatype: "bytea"`.
57
+
58
+ | `fieldTypeCd` | `baseDatatypeCd` | Description | Common `dbDatatype` | Params |
59
+ |---|---|---|---|---|
60
+ | `file` | `binary` | Arbitrary file upload. | `jsonb` | `accept`, `maxSize` |
61
+ | `image` | `binary` | Image upload with preview. | `jsonb` | `maxSize`, `dimensions` |
60
62
 
61
63
  ## Reference / lookup / set
62
64
 
@@ -72,6 +72,7 @@ For each entity:
72
72
  - [ ] Each source has `entityCode` and `columns`
73
73
  - [ ] `entityCode` points to an entity that exists
74
74
  - [ ] Column codes in `columns` all exist on the entity
75
+ - [ ] The rendered entity has **at least one visible scalar column** (a field with `V` in `flags`, `columnType` not `S`) — a grid/list/kanban/etc. view over an entity whose fields are all hidden (or only sets) is **rejected** at validate/pack and would render "No visible columns configured for this entity." `diagram`/`matrix`/`library` are exempt
75
76
  - [ ] If set, `viewType` is from the supported list (`grid`, `list`, `kanban`, `calendar`, `gallery`, `tree-grid`, `diagram`, `master-detail`, `library`, `matrix`)
76
77
  - [ ] A `matrix` view has a `viewConfig` with `rowAxis`, `colAxis`, and `cell` (cell `entity` matches the primary `dataSources` entity; `rowKey`/`colKey` are real cell columns)
77
78
  - [ ] Sort uses the view-def-root `order` key — a `string[]` like `["-created_date", "name"]` (leading `-` = descending), NOT `sort` / `[{column_cd, direction}]` (that object shape belongs to queries & reports, not data views)
@@ -172,6 +173,7 @@ If you see any of these, stop and investigate:
172
173
  - Menus with `children: [...]` (array, not dict)
173
174
  - Roles with `entityRights` (wrong key)
174
175
  - Data views with root-level `entityCode` (should be inside `dataSources`)
176
+ - A grid/list/kanban/etc. view over an entity with no field flagged `V` (or only set columns visible) — rejected at validate/pack
175
177
  - Formula columns without `baseDatatypeCd`
176
178
  - DSL actions with JavaScript/Python syntax
177
179
  - Seed data files without numbered prefixes