@dforge-core/dforge-mcp 0.1.5 → 0.1.7

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.5",
3
+ "version": "0.1.7",
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",
@@ -31,7 +31,7 @@
31
31
  "test": "vitest run"
32
32
  },
33
33
  "dependencies": {
34
- "@dforge-core/dforge-cli": "^0.2.2",
34
+ "@dforge-core/dforge-cli": "^0.2.7",
35
35
  "@dforge-core/metadata": "^0.0.5",
36
36
  "@modelcontextprotocol/sdk": "^1.29.0",
37
37
  "zod": "^4.4.3"
@@ -581,6 +581,11 @@ Cron-driven action fires. Each entry pairs an existing action (declared in `ui/a
581
581
  "department_id": { "label": "Department ID" },
582
582
  "department_name": { "label": "Department Name" }
583
583
  }
584
+ },
585
+ "employee": {
586
+ "constraints": {
587
+ "chk_salary_positive": { "message": "Salary must be positive" }
588
+ }
584
589
  }
585
590
  },
586
591
  "folders": {
@@ -599,7 +604,9 @@ Cron-driven action fires. Each entry pairs an existing action (declared in `ui/a
599
604
  }
600
605
  ```
601
606
 
602
- **Translatable sections (consumed by the installer):** `entities` (+ nested `fields`), `folders`, `views`, `menus` (+ nested `items`), `actions` (+ `params`), `reports` (+ `datasets.caption`, `params`).
607
+ **Translatable sections (consumed by the installer):** `entities` (+ nested `fields` and `constraints`), `folders`, `views`, `menus` (+ nested `items`), `actions` (+ `params`), `reports` (+ `datasets.caption`, `params`).
608
+
609
+ **Constraint violation messages are localizable (opt-in).** The `message` on a check/unique constraint in the entity JSON is the base (fallback) text. To localize it, add a per-locale override under `entities.<entityCd>.constraints.<constraintName>.message` in each `translations/<locale>.json`. The server resolves it with culture fallback (per-locale → base) and it surfaces identically on the client pre-save validator and the server DB-violation path. Unlike labels, constraint overrides are **not** completeness-enforced: a missing override for a declared `supportedLocales` entry emits a **non-fatal warning** (from `dforge_module_validate` pre-flight and at install) and the base message is used as the fallback.
603
610
 
604
611
  **Silently ignored at runtime:** `roles` and `print_templates` — the resource rows have no `res_id`, so translations are reserved for future use. `settings` is validated for completeness (when listed in `supportedLocales`) but the registrar does not display the translated labels, so don't expect localized output. Display names for these come from the source manifest (`description` for roles, `label` for print templates and settings).
605
612
 
@@ -116,7 +116,7 @@ Always-on cheat-sheet — enough to author inline; load the linked `references/*
116
116
  - **`toString`:** every entity needs one, `{column}` braces, e.g. `"{first_name} {last_name}"`.
117
117
  - **Data views:** `dataSources` array at root — never root-level `entityCode` + `columns`. → `data-views.md`
118
118
  - **Menus:** leaf items use `dataViewCode` (not `viewCode`); section nodes omit `itemType`; icons are Bootstrap names sans `bi-`. → `menus.md`
119
- - **Security roles:** `rights` (not `entityRights`); entities `SIUDC`, actions/reports/folders `E`. Rights keys: same-module entity bare (`product`), cross-module entity dotted (`fin.invoice`), and actions/reports/folders use a **colon** prefix — `action:approve`, `report:summary`, `folder:east` (never a dot). Omit a key to deny; never map to `""`. → `security.md`
119
+ - **Security roles:** `rights` (not `entityRights`); entities `SIUDC`, actions/reports/folders `E`. Rights keys: same-module entity bare (`product`), cross-module entity dotted (`fin.invoice`), and actions/reports/folders use a **colon** prefix — `action:approve`, `report:summary`, `folder:east` (never a dot). Omit a key to deny; never map to `""`. `roles.json` carries `description` (English fallback) + `rights` only — **no `label`**; the localized role display name lives in the translation files as `roles.<code>.label` and is completeness-enforced. → `security.md`, `translations.md`
120
120
  - **Action script** in `ui/actions.json` = bare filename (no path, no `.dsl`).
121
121
  - **Action DSL dates:** inside `execute:` use lowercase `now()`; `TODAY()`/`NOW()` are formula-only (`canExecute:`/formula columns) and are **undefined in `execute:`**. → `action-dsl.md`
122
122
  - **SQL placeholders** = `@paramName` (not `:paramName`).
@@ -397,7 +397,7 @@ Key areas (full checklist):
397
397
  - **Security**: every entity code in the manifest appears in at least one role's rights map; `rights` key used (not `entityRights`); entity rights use `SIUDC` letters; actions/reports use `E`.
398
398
  - **Actions**: every `script` value in `ui/actions.json` is a bare filename (no path, no `.dsl` extension); every action referenced by a trigger or job exists in `ui/actions.json`.
399
399
  - **Seed data**: numeric PKs; parent entities loaded before children; no circular references.
400
- - **Translations**: a `translations/<locale>.json` file exists for every locale in `supportedLocales`; every trait-provided field (`created_at`, `updated_at`, etc.) has a translation entry in each file.
400
+ - **Translations**: a `translations/<locale>.json` file exists for every locale in `supportedLocales` **plus the `en-US` base**; every trait-provided field (`created_at`, `updated_at`, etc.) has a translation entry in each file; a `roles` section carries a `label` for **every** role in `security/roles.json` (completeness-enforced in every locale incl. en-US — keys are module-qualified role codes like `crm.admin`).
401
401
 
402
402
  ### Step 3 — Translation deferral check
403
403
 
@@ -144,6 +144,7 @@ var note = params[note] // no quotes around param name in this DSL
144
144
  | `addDays(date, n)` | Date | Add `n` days to a date, e.g. `addDays(now(), 30)`. |
145
145
  | `nextNumber('entity')` | String | Generate next value from a number sequence. Usually not needed — platform auto-fills on `insert()`. Use for pre-generating numbers. |
146
146
  | `callProc('proc_name', { args })` | Result | Call a stored procedure. Args are passed as named parameters. |
147
+ | `entityLink('entityCd', record, description?)` | Link value | Build a clickable link to any entity record, for storing in an `entitylink` (jsonb) column. `record` is a row object (e.g. the result of `insert()` or `getRecord()`); its PK columns are read and stored as **strings**, so snowflake/cuid ids (> 2^53) stay exact. Optional `description` sets the link's display text. |
147
148
 
148
149
  > **Dates: `now()` in `execute:`, `TODAY()`/`NOW()` in formulas.** The `execute:` block runs as
149
150
  > JavaScript (Jint) and exposes **lowercase `now()`** only — `TODAY()` and `NOW()` (uppercase) are
@@ -67,6 +67,26 @@ Plus declare the FK constraint in the entity's `references` block:
67
67
  }
68
68
  ```
69
69
 
70
+ ### Referential actions — `onDelete` / `onUpdate` (optional)
71
+
72
+ A reference entry may declare `onDelete` and/or `onUpdate` to emit an `ON DELETE` / `ON UPDATE` clause on the FK. Both accept: `"cascade"`, `"setNull"`, `"restrict"`, `"noAction"`. **Omitted = `noAction`** (plain FK — the default and correct choice for most references).
73
+
74
+ ```json
75
+ "references": {
76
+ "FK_OpportunityLine_Opportunity": {
77
+ "from": { "field": "opportunity_id" },
78
+ "to": { "entity": "opportunity", "field": "opportunity_id" },
79
+ "onDelete": "cascade"
80
+ }
81
+ }
82
+ ```
83
+
84
+ - **`cascade`** — deleting the parent deletes its children. Use for owned child collections (line items under their header) so the parent delete isn't blocked by a FK violation.
85
+ - **`setNull`** — nulls the FK column when the parent is deleted; the FK column **must be nullable**.
86
+ - **`restrict` / `noAction`** — block the parent delete while children exist (the safe default; leave the keys off to get this).
87
+ - **`onUpdate`** — fires when the parent's key value changes. A **no-op for immutable `cuid` PKs** (identity-trait entities), so only meaningful for entities keyed on a natural/mutable PK.
88
+ - **Self-healing on reinstall:** the DDL generator reads the live FK's current rule and drops+recreates the FK only when it changed, so changing `onDelete` and reinstalling applies to already-provisioned tenants (and is a no-op otherwise). An unknown value fails install fast.
89
+
70
90
  > **FK column `dbDatatype` must exactly match the referenced entity's PK `dbDatatype`.** Never guess.
71
91
  > - Entities using the `identity` trait → PK is `dbDatatype: "cuid"` → FK column must also be `"cuid"`
72
92
  > - Cross-module or legacy entities → call `dforge_module_inspect` on the referenced module and read the PK column's `dbDatatype` before declaring the FK
@@ -65,7 +65,7 @@ Source of truth: `server/database/system-modules/metadata/seed-data/field_types.
65
65
  | `lookup` | `guid` | `R` | Reference to another entity (always paired with a hidden FK column — see SKILL.md "FK+Reference pattern") |
66
66
  | `user` | `cuid` | `D` | User picker. Writes a user ID directly, no paired FK needed. |
67
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). |
68
+ | `entitylink` | `json` | `D` | Polymorphic link to any entity (stores `{entity, id}` pair). Populate from action DSL with the `entityLink('entityCd', record, description?)` built-in (see `action-dsl.md`). |
69
69
 
70
70
  ## JSON
71
71
 
@@ -134,6 +134,8 @@ Check constraints use a subset of the formula grammar. The server parses them to
134
134
  [email] LIKE '%@%'
135
135
  ```
136
136
 
137
+ The constraint's `message` (the violation text shown to users) is **localizable** — add a per-locale override at `entities.<entityCd>.constraints.<constraintName>.message` in each `translations/<locale>.json`. The base `message` in the entity JSON is the fallback. See `translations.md` → "Constraint violation messages ARE translatable".
138
+
137
139
  ## Examples — full formula columns
138
140
 
139
141
  ### Full name
@@ -14,7 +14,11 @@ Lives in: `security/roles.json`
14
14
 
15
15
  ## Role definition
16
16
 
17
- Role codes use a `module.role` naming pattern (e.g. `crm.admin`, `crm.sales-rep`):
17
+ Role codes use a `module.role` naming pattern (e.g. `crm.admin`, `crm.sales-rep`).
18
+
19
+ > **Role display name & translations.** Each role has exactly two keys: `description` and `rights` (`roles.schema.json` is `additionalProperties: false` — **do not** add a `label` here). `description` is the English fallback display name. The **localized** display name lives in the translation files as `roles.<code>.label`, keyed by the module-qualified role code, and is **completeness-enforced**: every role must have a `roles.<code>.label` in every `translations/<locale>.json` (including the `en-US` base) or install fails. See `translations.md`.
20
+
21
+ Example:
18
22
 
19
23
  ```json
20
24
  {
@@ -33,6 +33,10 @@ Translation files mirror the module's content structure. Each translatable objec
33
33
  "folders": {
34
34
  "crm": { "label": "Sales CRM" }
35
35
  },
36
+ "roles": {
37
+ "crm.admin": { "label": "CRM Administrator" },
38
+ "crm.sales-rep": { "label": "Sales Representative" }
39
+ },
36
40
  "views": {
37
41
  "contacts": { "label": "Contacts" },
38
42
  "contacts_list": { "label": "Contact List" },
@@ -75,14 +79,49 @@ Translation files mirror the module's content structure. Each translatable objec
75
79
  |---|---|---|
76
80
  | `entities` | Entity labels (`label`, `desc`) and all field labels | Entity codes → `{ label, desc, fields: { field_code: { label } } }` |
77
81
  | `folders` | Folder labels | Folder codes (root folder key from `ui/folders.json`) |
82
+ | `roles` | Role display labels | Role codes (keys from `security/roles.json`, already module-qualified — e.g. `crm.admin`) → `{ label }` |
78
83
  | `views` | Data view labels | View codes (keys from `ui/data_views.json`) |
79
84
  | `menus` | Menu node labels (root + nested items) | Menu root key → `{ label, items: { item_code: { label } } }` matching `ui/menus.json` structure |
80
85
  | `actions` | Action labels, descriptions, and param labels | Action codes (keys from `ui/actions.json`) → `{ label, desc, params: { param_cd: { label } } }` |
81
86
  | `reports` | Report dataset captions and param labels | Report codes → `{ datasets: { ds_cd: { caption } }, params: { param_cd: { label } } }` |
87
+ | `entities.<e>.constraints` | Check/unique constraint violation messages (opt-in — warned, not enforced) | Constraint codes → `{ message }` under the owning entity |
88
+
89
+ ### Constraint violation messages ARE translatable (opt-in)
90
+
91
+ A check/unique constraint declares a user-facing `message` in the entity JSON (`constraints.<name>.message`). That message is the **base/fallback text**. To localize it, add a per-locale override under `entities.<entityCd>.constraints.<constraintName>.message`:
92
+
93
+ ```json
94
+ "entities": {
95
+ "employee": {
96
+ "constraints": {
97
+ "chk_salary_positive": { "message": "Gehalt muss positiv sein" },
98
+ "UQ_employee_code": { "message": "Mitarbeiter-Code muss eindeutig sein" }
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ - The server resolves the message with culture fallback — **per-locale override → base message from the entity JSON**. `EntityMetadataLoader` reads `COALESCE(culture_res.label, resource.label, entity_constrains.description)`, so the same override surfaces on the client pre-save `CheckConstraints` validator and the server DB-violation (23514) path.
105
+ - **Opt-in, never mandatory** (unlike roles/labels, which completeness *enforces*). If the manifest lists `supportedLocales` and a constraint `message` has no override for one of those (non-English) locales, install emits a **non-fatal warning** naming the missing `entities.<e>.constraints.<c>.message` keys (printed by the CLI, returned in the marketplace install response). Install still commits with the base message as the fallback. `dforge_module_validate` surfaces the same gap pre-flight as a warning.
106
+ - Only constraints that declare a `message` are scanned; English (`en`/`en-*`) is the base and is never warned; extension entities (`"extends": "..."`) are skipped — their constraint translations belong with the foreign module's files.
107
+
108
+ ### Roles ARE translated — and completeness is enforced
109
+
110
+ Role labels are **required and localized** (this changed — older docs said roles were skipped; they are not). `SecurityRegistrar` gives every role a `res_id`, so `TranslationRegistrar` wires the role label exactly like entities/views/menus:
111
+
112
+ ```json
113
+ "roles": {
114
+ "crm.admin": { "label": "CRM Administrator" },
115
+ "crm.sales-rep": { "label": "Sales Representative" }
116
+ }
117
+ ```
118
+
119
+ - **Keys are the role codes** from `security/roles.json` — already module-qualified (e.g. `crm.admin`, `crm.sales-rep`). They match `module_role.name`.
120
+ - **Value is `{ "label": ... }`** — the localized display name. It supersedes the `description` from `roles.json` at runtime; `description` remains the English fallback if a role has no translation entry.
121
+ - **Completeness is enforced.** `TranslationCompletenessValidator` requires `roles.<code>.label` for **every** role in `security/roles.json`, in **every** translation file — including the `en-US` base. A missing role label fails install with `Label for role '<code>'.` So ship a `roles` block with a label for each role in **each** `translations/<locale>.json` (en-US + every locale in `supportedLocales`).
82
122
 
83
123
  ### Sections that are NOT translated (write-time only)
84
124
 
85
- - `roles` — role rows have no `res_id` column. The `roles` key may appear in shipped translation files (e.g. `modules/crm/translations/de-DE.json`) but is **explicitly skipped** by the installer. Role display name comes from `security/roles.json` `description` only. (Comment in [`TranslationRegistrar.cs:248`](../../../server/src/dForge.Admin/Services/ModuleInstall/TranslationRegistrar.cs#L248): "reserved for future use.")
86
125
  - `settings` — completeness *is* checked by the validator when listed in `manifest.supportedLocales` (so you'll be forced to ship `settings.<cd>.label` for each declared locale), but the registrar doesn't display those translated labels at runtime. The English `label` in `settings.json` is what users see today.
87
126
  - `print_templates` — not currently consumed by the registrar. Template labels come from `ui/print_templates.json`.
88
127
 
@@ -112,6 +151,10 @@ If you include these sections, the install succeeds (they're silently ignored)
112
151
  "folders": {
113
152
  "crm": { "label": "Vertrieb CRM" }
114
153
  },
154
+ "roles": {
155
+ "crm.admin": { "label": "CRM-Administrator" },
156
+ "crm.sales-rep": { "label": "Vertriebsmitarbeiter" }
157
+ },
115
158
  "views": {
116
159
  "contacts": { "label": "Kontakte" }
117
160
  },
@@ -144,12 +187,13 @@ Every listed locale MUST have a matching `translations/<locale>.json` with a lab
144
187
 
145
188
  1. **Always provide `en-US`** as the base language. Other languages fall back to it for missing keys.
146
189
  2. **Locale format is `ll-CC`** (e.g. `en-US`, `de-DE`, `fr-FR`) — language code + country code, hyphen-separated.
147
- 3. **Translate everything user-visible**: entity labels, every field label (including trait-provided fields like `created_date`, `last_updated`), view names, menu items, action labels, role descriptions, setting labels, folder names.
190
+ 3. **Translate everything user-visible**: entity labels, every field label (including trait-provided fields like `created_date`, `last_updated`), view names, menu items, action labels, **role labels (required — completeness-enforced, `roles.<code>.label` for every role in every locale incl. en-US)**, setting labels, folder names.
148
191
  4. **Include virtual columns** (references, sets, formulas) in field translations — they appear in the UI just like physical columns.
149
192
  5. **Menu translations mirror the `ui/menus.json` structure** — root key → `items` → nested items.
150
193
  6. **Missing keys fall back** to the `en-US` value, then to the raw code name. So it's safe to ship partial translations — untranslated items show in English rather than breaking.
151
194
  7. **Don't translate column codes** — only the `label` values. Codes stay as-is in all languages.
152
195
  8. **File naming**: `translations/<locale>.json` (e.g. `de-DE.json`) — each non-English file must match a locale listed in `supportedLocales`.
196
+ 9. **Constraint messages are opt-in** — localize them under `entities.<e>.constraints.<c>.message` when you want a per-locale violation message; otherwise the base `message` from the entity JSON is used. Missing overrides warn (non-fatal), they don't fail install. See "Constraint violation messages ARE translatable" above.
153
197
 
154
198
  ## When to create translations
155
199
 
@@ -90,7 +90,7 @@ For each entity:
90
90
  ## Security
91
91
 
92
92
  - [ ] `security/roles.json` exists (at least one role)
93
- - [ ] Every role has `description` and `rights` (both required; no `label` field display name comes from `description`). The `roles.schema.json` sets `additionalProperties: false`, so any extra field (including `label`) is rejected at install time.
93
+ - [ ] Every role has `description` and `rights` (both required; no `label` field in `roles.json` the `roles.schema.json` sets `additionalProperties: false`, so any extra field including `label` is rejected here). `description` is the English display-name fallback; **localized role labels live in the translation files** (`roles.<code>.label`) and are completeness-enforced — see Translations below.
94
94
  - [ ] The field name is `rights`, NOT `entityRights`
95
95
  - [ ] Rights strings use only valid letters: `SIUDC` for entities, `E` for actions/reports/folders
96
96
  - [ ] Entity codes in `rights` all reference real entities (in this module or dependencies)
@@ -132,7 +132,8 @@ For each action:
132
132
  - [ ] Has `views` section with labels for every data view
133
133
  - [ ] Has `menus` section matching the `ui/menus.json` structure
134
134
  - [ ] Has `actions` section with labels for every action
135
- - [ ] Has `roles` section with labels for every role
135
+ - [ ] Has `roles` section with a `label` for **every** role in `security/roles.json` (completeness-enforced — a missing role label fails install; keys are the module-qualified role codes, e.g. `crm.admin`)
136
+ - [ ] (Optional) Constraint messages localized under `entities.<e>.constraints.<c>.message` — opt-in; a missing override for a declared `supportedLocales` entry warns (surfaced by `dforge_module_validate` and at install) but does not fail install
136
137
  - [ ] Has `settings` section with labels for every setting (if settings exist)
137
138
  - [ ] Has `folders` section with label for the root folder
138
139
  - [ ] Additional language files (if any) have the same structure as `en-US.json`