@dforge-core/metadata 0.0.31 → 0.0.33
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 +62 -0
- package/dist/dsl/index.d.ts +25 -3
- package/dist/dsl/index.js +249 -23
- package/dist/dsl/index.js.map +1 -1
- package/dist/index.d.ts +88 -11
- package/dist/index.js.map +1 -1
- package/package.json +67 -66
- package/schemas/entity.schema.json +17 -13
- package/schemas/manifest.schema.json +2 -2
- package/schemas/traits.schema.json +5 -0
- package/schemas/translations.schema.json +267 -0
- package/src/dsl/builtins.ts +12 -7
- package/src/dsl/check.ts +42 -1
- package/src/dsl/index.ts +3 -0
- package/src/dsl/lexer.ts +39 -14
- package/src/dsl/messages.ts +196 -0
- package/src/dsl/types.ts +7 -0
- package/src/entity.ts +8 -8
- package/src/index.ts +11 -1
- package/src/manifest.ts +4 -2
- package/src/translations.ts +61 -0
package/package.json
CHANGED
|
@@ -1,67 +1,68 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
2
|
+
"name": "@dforge-core/metadata",
|
|
3
|
+
"version": "0.0.33",
|
|
4
|
+
"description": "Canonical dForge authoring registries — field types, base datatypes, column types, traits, data-view kinds and chart types — with derivation helpers. Runtime-dependency-free; the single source of truth shared by editors, the web app and the CLI.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "dForge",
|
|
7
|
+
"homepage": "https://github.com/iash44/dForge-core/tree/main/clients/packages/metadata#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/iash44/dForge-core.git",
|
|
11
|
+
"directory": "clients/packages/metadata"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/iash44/dForge-core/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"dforge",
|
|
18
|
+
"metadata",
|
|
19
|
+
"field-type",
|
|
20
|
+
"trait",
|
|
21
|
+
"registry",
|
|
22
|
+
"low-code"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"source": "./src/index.ts",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./dsl": {
|
|
38
|
+
"source": "./src/dsl/index.ts",
|
|
39
|
+
"types": "./dist/dsl/index.d.ts",
|
|
40
|
+
"default": "./dist/dsl/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./schemas/*": "./schemas/*",
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"sideEffects": false,
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"src",
|
|
49
|
+
"schemas",
|
|
50
|
+
"CHANGELOG.md"
|
|
51
|
+
],
|
|
52
|
+
"scripts": {
|
|
53
|
+
"sync-schemas": "node scripts/sync-schemas.mjs",
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"prepack": "pnpm sync-schemas && pnpm typecheck && pnpm build",
|
|
59
|
+
"package": "pnpm pack"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^25.9.4",
|
|
63
|
+
"ajv": "^8.20.0",
|
|
64
|
+
"tsup": "^8.5.1",
|
|
65
|
+
"typescript": "^6.0.3",
|
|
66
|
+
"vitest": "^4.1.10"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://dforge.dev/schemas/entity.schema.json",
|
|
4
4
|
"title": "dForge Entity Definition",
|
|
5
|
-
"description": "Defines an entity (table
|
|
5
|
+
"description": "Defines an entity (a table) for a dForge module",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"anyOf": [
|
|
8
8
|
{ "required": ["fields"] },
|
|
@@ -24,16 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dbObject": {
|
|
26
26
|
"type": "string",
|
|
27
|
-
"description": "Database table
|
|
28
|
-
},
|
|
29
|
-
"isView": {
|
|
30
|
-
"type": "boolean",
|
|
31
|
-
"description": "When true, entity maps to a SQL view (read-only, no DDL generation). Default: false (table-backed).",
|
|
32
|
-
"default": false
|
|
33
|
-
},
|
|
34
|
-
"viewSql": {
|
|
35
|
-
"type": "string",
|
|
36
|
-
"description": "SQL SELECT statement for view-backed entities (isView=true). Executed as CREATE OR REPLACE VIEW during module install."
|
|
27
|
+
"description": "Database table name (defaults to entity code)"
|
|
37
28
|
},
|
|
38
29
|
"toString": {
|
|
39
30
|
"description": "Display pattern using column placeholders, e.g. \"{first_name} {last_name}\" — the caption of a record wherever it is referenced. Every `{placeholder}` must name a field of the entity (trait columns included) and not a set; pack and install fail otherwise. Omitted, the install generates one from the fields and warns. Not declared on extension entities (with `extends`). No `type` constraint here because `toString` collides with Object.prototype.toString in JSON validators — they read the inherited function and reject it, even when the property is absent from the JSON."
|
|
@@ -42,6 +33,11 @@
|
|
|
42
33
|
"type": "boolean",
|
|
43
34
|
"description": "Records of this entity accept comments \u2014 the composer and thread appear on the record card, beside the audit history. Opt-in per entity; `comments` in the manifest sets a module-wide default that this overrides in both directions."
|
|
44
35
|
},
|
|
36
|
+
"auditHistory": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"enum": ["basic", "fields", "full"],
|
|
39
|
+
"description": "Audit history level for this entity, overriding the manifest's `auditHistory`: 'basic' logs each insert/update/delete without field details; 'fields' adds old/new values of changed fields; 'full' also stores display values (reference and user names). Omit to disable."
|
|
40
|
+
},
|
|
45
41
|
"traits": {
|
|
46
42
|
"type": "array",
|
|
47
43
|
"description": "Entity traits expanded during module install (authoring shortcut). Trait definitions loaded from DB (seeded by metadata module).",
|
|
@@ -146,7 +142,7 @@
|
|
|
146
142
|
},
|
|
147
143
|
"views": {
|
|
148
144
|
"type": "object",
|
|
149
|
-
"description": "Entity views keyed by view name — column-level security. Unrelated to
|
|
145
|
+
"description": "Entity views keyed by view name — column-level security. Unrelated 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.",
|
|
150
146
|
"propertyNames": {
|
|
151
147
|
"pattern": "^[a-zA-Z_][a-zA-Z0-9_-]*$",
|
|
152
148
|
"maxLength": 100
|
|
@@ -364,6 +360,14 @@
|
|
|
364
360
|
"serverDefault": {
|
|
365
361
|
"$ref": "#/$defs/serverDefault"
|
|
366
362
|
},
|
|
363
|
+
"currency": {
|
|
364
|
+
"type": "string",
|
|
365
|
+
"description": "currency field type: fixed ISO 4217 code (e.g. 'USD') the value is formatted in. With currencyColumn, the code used for a record whose currencyColumn is empty."
|
|
366
|
+
},
|
|
367
|
+
"currencyColumn": {
|
|
368
|
+
"type": "string",
|
|
369
|
+
"description": "currency field type: a scalar column of the same entity holding the ISO 4217 code per record, e.g. a 'currency' dropdown. Each value is formatted in its own record's currency; a total over rows in different currencies shows no symbol. A line reads its header's code through a formula column ([invoice].[currency]). Display only — nothing converts between currencies."
|
|
370
|
+
},
|
|
367
371
|
"pattern": {
|
|
368
372
|
"type": "string",
|
|
369
373
|
"description": "Format rule for this column, as an ECMAScript regex source. Overrides the field type's own pattern (field_type.def_params.pattern, which is what makes email/phone/url reject a malformed value). Enforced in the browser and again on data.insert / data.update, so keep to constructs both engines read alike: [0-9] and \\d are the same there, \\s is normalized to JavaScript's set, and a pattern that will not compile is skipped by both rather than enforced by one."
|
|
@@ -388,7 +392,7 @@
|
|
|
388
392
|
"description": "Reference to a reusable column domain, qualified as 'module_cd.domain_cd' (unqualified resolves within this module, then tenant-created domains). The domain supplies dbDatatype, baseDatatypeCd, fieldTypeCd, maxLen, precision and params; declaring any of those alongside 'domain' is rejected at install. The column keeps its own description, flags, orderNum, isNullable and isPk."
|
|
389
393
|
},
|
|
390
394
|
"refFilter": {
|
|
391
|
-
"description": "Filter criteria for reference lookups. Condition values support @[field] placeholders resolved against the current record at lookup time (e.g. {\"c\":\"channel\",\"o\":\"eq\",\"v\":\"@[channel]\"} narrows options to rows matching the record's own channel)
|
|
395
|
+
"description": "Filter criteria for reference lookups. Condition values support @[field] placeholders resolved against the current record at lookup time (e.g. {\"c\":\"channel\",\"o\":\"eq\",\"v\":\"@[channel]\"} narrows options to rows matching the record's own channel), and @[ref].[field] nav placeholders that read through a Reference column, as a formula does (e.g. @[purchase_order].[supplier_id] on an order line narrows to the order's supplier, also in the New dialog of the order's Lines set). A condition whose placeholder is unset is dropped rather than matching nothing."
|
|
392
396
|
}
|
|
393
397
|
},
|
|
394
398
|
"oneOf": [
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
},
|
|
105
105
|
"auditHistory": {
|
|
106
106
|
"type": "string",
|
|
107
|
-
"enum": ["
|
|
108
|
-
"description": "Default audit history
|
|
107
|
+
"enum": ["basic", "fields", "full"],
|
|
108
|
+
"description": "Default audit history level for entities in this module: 'basic' logs each insert/update/delete without field details; 'fields' adds old/new values of changed fields; 'full' also stores display values (reference and user names). Omit to disable. Individual entities can override."
|
|
109
109
|
},
|
|
110
110
|
"comments": {
|
|
111
111
|
"type": "boolean",
|
|
@@ -56,6 +56,11 @@
|
|
|
56
56
|
"formula": { "type": "string" },
|
|
57
57
|
"orderNum": { "type": "integer" },
|
|
58
58
|
"description": { "type": "string", "description": "Supports {entity}/{Entity} placeholders" },
|
|
59
|
+
"labels": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"description": "Translated labels keyed by culture code (e.g. de-DE). Applied to every entity that takes the field from this trait; the module's translations/<locale>.json overrides them per entity. Supports {entity}/{Entity} placeholders.",
|
|
62
|
+
"additionalProperties": { "type": "string" }
|
|
63
|
+
},
|
|
59
64
|
"maxLen": { "type": "integer" },
|
|
60
65
|
"link": {
|
|
61
66
|
"type": "object",
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://dforge.dev/schemas/translations.schema.json",
|
|
4
|
+
"title": "dForge Translations",
|
|
5
|
+
"description": "A module's translations/<locale>.json, e.g. de-DE.json. English is the base text authored in the other module files, so an en-* file is not applied at install. Every key is optional: a missing entry falls back to the base text. Labels are required only for the locales the manifest lists in supportedLocales (constraint messages and DSL messages are warned about, never required).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"entities": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"description": "Entity code → labels for the entity, its fields, card sections and constraint messages.",
|
|
15
|
+
"additionalProperties": {
|
|
16
|
+
"$ref": "#/$defs/entity"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"domains": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"description": "Column domain code (domains.json) → label and option labels, translated once for every column that uses the domain.",
|
|
22
|
+
"additionalProperties": {
|
|
23
|
+
"$ref": "#/$defs/withOptions"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"folders": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"description": "Folder code → label. A sub-folder is keyed by its own flat code, not its path.",
|
|
29
|
+
"additionalProperties": {
|
|
30
|
+
"$ref": "#/$defs/labelOnly"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"views": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"description": "Data view code → label and description (the subtitle under the view title).",
|
|
36
|
+
"additionalProperties": {
|
|
37
|
+
"$ref": "#/$defs/labelDesc"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"menus": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"description": "Menu code → label and item labels.",
|
|
43
|
+
"additionalProperties": {
|
|
44
|
+
"$ref": "#/$defs/menu"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"settings": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"description": "Setting code (settings.json) → label and description.",
|
|
50
|
+
"additionalProperties": {
|
|
51
|
+
"$ref": "#/$defs/labelDesc"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"roles": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"description": "Module-qualified role code (e.g. \"crm.admin\") → label.",
|
|
57
|
+
"additionalProperties": {
|
|
58
|
+
"$ref": "#/$defs/labelOnly"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"reports": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"description": "Report code → label, dataset captions and parameter labels.",
|
|
64
|
+
"additionalProperties": {
|
|
65
|
+
"$ref": "#/$defs/report"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"actions": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"description": "Action code (ui/actions.json) → label, tooltip and parameter labels.",
|
|
71
|
+
"additionalProperties": {
|
|
72
|
+
"$ref": "#/$defs/action"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"print_templates": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"description": "Print template code → label. Accepted, but not applied at install yet.",
|
|
78
|
+
"additionalProperties": {
|
|
79
|
+
"$ref": "#/$defs/labelOnly"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"messages": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"description": "Text of a DSL info() / warn() / error() / exit() call, exactly as written in the .dsl file → its translation. {name} placeholders are filled from the call's values and may be reordered. See docs/business-logic/message-localization.md.",
|
|
85
|
+
"propertyNames": {
|
|
86
|
+
"minLength": 1
|
|
87
|
+
},
|
|
88
|
+
"additionalProperties": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"minLength": 1
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"$defs": {
|
|
95
|
+
"label": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"description": "Translated display label."
|
|
98
|
+
},
|
|
99
|
+
"labelOnly": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"additionalProperties": false,
|
|
102
|
+
"properties": {
|
|
103
|
+
"label": {
|
|
104
|
+
"$ref": "#/$defs/label"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"labelDesc": {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"additionalProperties": false,
|
|
111
|
+
"properties": {
|
|
112
|
+
"label": {
|
|
113
|
+
"$ref": "#/$defs/label"
|
|
114
|
+
},
|
|
115
|
+
"desc": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "Translated description or tooltip."
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"option": {
|
|
122
|
+
"description": "A translated option: the label alone, or the label with an icon and color override.",
|
|
123
|
+
"oneOf": [
|
|
124
|
+
{
|
|
125
|
+
"type": "string"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"type": "object",
|
|
129
|
+
"additionalProperties": false,
|
|
130
|
+
"properties": {
|
|
131
|
+
"label": {
|
|
132
|
+
"type": "string"
|
|
133
|
+
},
|
|
134
|
+
"icon": {
|
|
135
|
+
"type": "string"
|
|
136
|
+
},
|
|
137
|
+
"color": {
|
|
138
|
+
"type": "string"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
"options": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"description": "Option value (the stored code) → its translation.",
|
|
147
|
+
"additionalProperties": {
|
|
148
|
+
"$ref": "#/$defs/option"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"withOptions": {
|
|
152
|
+
"type": "object",
|
|
153
|
+
"additionalProperties": false,
|
|
154
|
+
"properties": {
|
|
155
|
+
"label": {
|
|
156
|
+
"$ref": "#/$defs/label"
|
|
157
|
+
},
|
|
158
|
+
"options": {
|
|
159
|
+
"$ref": "#/$defs/options"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"entity": {
|
|
164
|
+
"type": "object",
|
|
165
|
+
"additionalProperties": false,
|
|
166
|
+
"properties": {
|
|
167
|
+
"label": {
|
|
168
|
+
"$ref": "#/$defs/label"
|
|
169
|
+
},
|
|
170
|
+
"desc": {
|
|
171
|
+
"type": "string"
|
|
172
|
+
},
|
|
173
|
+
"fields": {
|
|
174
|
+
"type": "object",
|
|
175
|
+
"description": "Column code → label and, for dropdown / radio / flags columns, option labels.",
|
|
176
|
+
"additionalProperties": {
|
|
177
|
+
"$ref": "#/$defs/withOptions"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
"columnGroups": {
|
|
181
|
+
"type": "object",
|
|
182
|
+
"description": "Card-layout section code → heading.",
|
|
183
|
+
"additionalProperties": {
|
|
184
|
+
"$ref": "#/$defs/labelOnly"
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
"constraints": {
|
|
188
|
+
"type": "object",
|
|
189
|
+
"description": "Check / unique constraint name → violation message.",
|
|
190
|
+
"additionalProperties": {
|
|
191
|
+
"type": "object",
|
|
192
|
+
"additionalProperties": false,
|
|
193
|
+
"properties": {
|
|
194
|
+
"message": {
|
|
195
|
+
"type": "string"
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
"menu": {
|
|
203
|
+
"type": "object",
|
|
204
|
+
"additionalProperties": false,
|
|
205
|
+
"properties": {
|
|
206
|
+
"label": {
|
|
207
|
+
"$ref": "#/$defs/label"
|
|
208
|
+
},
|
|
209
|
+
"items": {
|
|
210
|
+
"type": "object",
|
|
211
|
+
"description": "Menu item code → label. Flat even when ui/menus.json nests items under children: a group and its children are siblings here.",
|
|
212
|
+
"additionalProperties": {
|
|
213
|
+
"$ref": "#/$defs/labelOnly"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
"params": {
|
|
219
|
+
"type": "object",
|
|
220
|
+
"description": "Parameter code → label and dropdown option labels.",
|
|
221
|
+
"additionalProperties": {
|
|
222
|
+
"$ref": "#/$defs/withOptions"
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
"report": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"additionalProperties": false,
|
|
228
|
+
"properties": {
|
|
229
|
+
"label": {
|
|
230
|
+
"$ref": "#/$defs/label"
|
|
231
|
+
},
|
|
232
|
+
"datasets": {
|
|
233
|
+
"type": "object",
|
|
234
|
+
"description": "Dataset code → caption.",
|
|
235
|
+
"additionalProperties": {
|
|
236
|
+
"type": "object",
|
|
237
|
+
"additionalProperties": false,
|
|
238
|
+
"properties": {
|
|
239
|
+
"caption": {
|
|
240
|
+
"type": "string"
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"params": {
|
|
246
|
+
"$ref": "#/$defs/params"
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
"action": {
|
|
251
|
+
"type": "object",
|
|
252
|
+
"additionalProperties": false,
|
|
253
|
+
"properties": {
|
|
254
|
+
"label": {
|
|
255
|
+
"$ref": "#/$defs/label"
|
|
256
|
+
},
|
|
257
|
+
"desc": {
|
|
258
|
+
"type": "string",
|
|
259
|
+
"description": "Translated tooltip."
|
|
260
|
+
},
|
|
261
|
+
"params": {
|
|
262
|
+
"$ref": "#/$defs/params"
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
package/src/dsl/builtins.ts
CHANGED
|
@@ -74,22 +74,27 @@ export const BUILTINS: Builtin[] = [
|
|
|
74
74
|
// Messaging
|
|
75
75
|
{
|
|
76
76
|
name: "error",
|
|
77
|
-
signature: "error(message)",
|
|
77
|
+
signature: "error(message, values?)",
|
|
78
78
|
returns: "never",
|
|
79
|
-
doc: "Abort the action and show an error. Rolls back all changes.",
|
|
79
|
+
doc: "Abort the action and show an error. Rolls back all changes. The text is looked up in the module's `messages` translations; `{name}` placeholders are filled from `values`.",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "warn",
|
|
83
|
+
signature: "warn(message, values?, opts?)",
|
|
84
|
+
returns: "void",
|
|
85
|
+
doc: "Show a warning and continue. The text is looked up in the module's `messages` translations; `{name}` placeholders are filled from `values`.",
|
|
80
86
|
},
|
|
81
|
-
{ name: "warn", signature: "warn(message)", returns: "void", doc: "Show a warning and continue." },
|
|
82
87
|
{
|
|
83
88
|
name: "info",
|
|
84
|
-
signature: "info(message)",
|
|
89
|
+
signature: "info(message, values?, opts?)",
|
|
85
90
|
returns: "void",
|
|
86
|
-
doc: "Show an informational message — a receipt for work the action did, not a way to publish a computed value.",
|
|
91
|
+
doc: "Show an informational message — a receipt for work the action did, not a way to publish a computed value. The text is looked up in the module's `messages` translations; `{name}` placeholders are filled from `values`. `opts.links` adds record links.",
|
|
87
92
|
},
|
|
88
93
|
{
|
|
89
94
|
name: "exit",
|
|
90
|
-
signature: "exit(message?, level?)",
|
|
95
|
+
signature: "exit(message?, level?, values?)",
|
|
91
96
|
returns: "never",
|
|
92
|
-
doc: "Stop the script early **without** error, keeping changes made so far. Level `'info'` (default) or `'
|
|
97
|
+
doc: "Stop the script early **without** error, keeping changes made so far. Level `'info'` (default), `'warning'`, `'danger'` or `'success'`. The message is looked up in the module's `messages` translations; `{name}` placeholders are filled from `values`. This is the DSL's early return — the block compiles to a bare script, so a `return` outside a function is a syntax error wherever it sits.",
|
|
93
98
|
},
|
|
94
99
|
{
|
|
95
100
|
name: "notify",
|
package/src/dsl/check.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// omits, and the rules that depend on it stand down. See ./types.
|
|
12
12
|
|
|
13
13
|
import { BUILTIN_BY_NAME } from "./builtins";
|
|
14
|
+
import { extractMessages } from "./messages";
|
|
14
15
|
import { CONTROL_KEYWORDS, type Token } from "./lexer";
|
|
15
16
|
import {
|
|
16
17
|
type BlockKind,
|
|
@@ -25,7 +26,7 @@ import type { ColumnLookup, DslContext, DslIssue, DslSeverity } from "./types";
|
|
|
25
26
|
* Check a DSL body. Returns [] for a clean script.
|
|
26
27
|
*
|
|
27
28
|
* Issues come back in source-feature order (field reads, params, execution
|
|
28
|
-
* mode, block spelling, built-ins, top-level returns, inline assignments)
|
|
29
|
+
* mode, block spelling, built-ins, top-level returns, inline assignments, messages)
|
|
29
30
|
* rather than sorted by position — a host that wants them in file order can
|
|
30
31
|
* sort on `start`.
|
|
31
32
|
*/
|
|
@@ -573,9 +574,49 @@ export function checkDsl(text: string, ctx: DslContext = {}): DslIssue[] {
|
|
|
573
574
|
}
|
|
574
575
|
|
|
575
576
|
out.push(...inlineAssignmentIssues(parsed, positionAt));
|
|
577
|
+
|
|
578
|
+
// ── messages ─────────────────────────────────────────────────────
|
|
579
|
+
// Install reports the same, module-wide (ActionMessageScanner).
|
|
580
|
+
const locales = Object.entries(ctx.messageTranslations ?? {});
|
|
581
|
+
for (const m of extractMessages(parsed)) {
|
|
582
|
+
if (m.kind === "concatenated" && locales.length > 0) {
|
|
583
|
+
add(
|
|
584
|
+
m.span,
|
|
585
|
+
`This ${m.builtin}() message joins text and values, so it cannot be translated. Put the values in {placeholders}: ${m.builtin}('Only {n} left', ${m.builtin === "exit" ? "'warning', " : ""}{ n: qty }).`,
|
|
586
|
+
"warning",
|
|
587
|
+
"dsl/message-concatenated",
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
if (m.text === undefined) continue;
|
|
591
|
+
|
|
592
|
+
// A call without values keeps a literal "{x}" as text, as before placeholders existed.
|
|
593
|
+
if (m.valueNames && m.valueNames.length > 0) {
|
|
594
|
+
for (const name of placeholders(m.text)) {
|
|
595
|
+
if (!m.valueNames.includes(name))
|
|
596
|
+
add(m.span, `{${name}} has no value in this call, so it shows as written.`, "warning", "dsl/message-placeholder");
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// A blank translation is dropped at install, so the message still shows in English.
|
|
601
|
+
const missing = locales
|
|
602
|
+
.filter(([, messages]) => !(Object.hasOwn(messages, m.text!) && messages[m.text!]?.trim()))
|
|
603
|
+
.map(([locale]) => locale);
|
|
604
|
+
if (missing.length > 0) {
|
|
605
|
+
add(
|
|
606
|
+
m.span,
|
|
607
|
+
`No ${missing.join(", ")} translation for this message — add it to "messages" in translations/<locale>.json, keyed by the exact text.`,
|
|
608
|
+
"warning",
|
|
609
|
+
"dsl/message-untranslated",
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
576
613
|
return out;
|
|
577
614
|
}
|
|
578
615
|
|
|
616
|
+
function placeholders(text: string): string[] {
|
|
617
|
+
return [...new Set([...text.matchAll(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g)].map((x) => x[1]!))];
|
|
618
|
+
}
|
|
619
|
+
|
|
579
620
|
/**
|
|
580
621
|
* Column matching is case-insensitive at the other end — `EntityColumnLookup`
|
|
581
622
|
* hands the compiler an OrdinalIgnoreCase set — but a host passes us whatever
|
package/src/dsl/index.ts
CHANGED
|
@@ -27,6 +27,9 @@ export type {
|
|
|
27
27
|
export { BUILTINS, BUILTIN_BY_NAME, BUILTIN_VALUES } from "./builtins";
|
|
28
28
|
export type { Builtin } from "./builtins";
|
|
29
29
|
|
|
30
|
+
export { extractMessages } from "./messages";
|
|
31
|
+
export type { DslMessage, DslMessageKind } from "./messages";
|
|
32
|
+
|
|
30
33
|
export { checkDsl } from "./check";
|
|
31
34
|
export type {
|
|
32
35
|
ColumnLookup,
|