@dforge-core/metadata 0.0.49 → 0.0.51

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/metadata",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
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
5
  "license": "MIT",
6
6
  "author": "dForge",
@@ -14,7 +14,7 @@
14
14
  "properties": {
15
15
  "viewType": {
16
16
  "type": "string",
17
- "enum": ["grid", "list", "kanban", "calendar", "gallery", "tree-grid", "diagram", "library", "matrix"],
17
+ "enum": ["grid", "list", "checklist", "kanban", "calendar", "gallery", "tree-grid", "diagram", "library", "matrix"],
18
18
  "default": "grid",
19
19
  "description": "View display type. Optional — omitted or unknown values fall back to 'grid' at runtime."
20
20
  },
@@ -118,6 +118,18 @@
118
118
  }
119
119
  }
120
120
  },
121
+ "checklist": {
122
+ "if": {
123
+ "properties": { "viewType": { "const": "checklist" } },
124
+ "required": ["viewType"]
125
+ },
126
+ "then": {
127
+ "properties": {
128
+ "viewConfig": { "$ref": "#/$defs/checklistViewConfig" }
129
+ },
130
+ "required": ["viewConfig"]
131
+ }
132
+ },
121
133
  "diagram": {
122
134
  "if": {
123
135
  "properties": { "viewType": { "const": "diagram" } },
@@ -249,6 +261,47 @@
249
261
  },
250
262
  "additionalProperties": false
251
263
  },
264
+ "checklistViewConfig": {
265
+ "type": "object",
266
+ "required": ["doneField"],
267
+ "properties": {
268
+ "titleField": {
269
+ "type": "string",
270
+ "description": "Column code of the row title; defaults to the record's display name. Also the column an inline add row types into"
271
+ },
272
+ "doneField": {
273
+ "type": "string",
274
+ "description": "Column the row's done circle reads and writes: a bool column, or a dropdown column with doneValue"
275
+ },
276
+ "doneValue": {
277
+ "type": "string",
278
+ "description": "Option value of a dropdown doneField that means done (ignored for a bool doneField)"
279
+ },
280
+ "undoneValue": {
281
+ "type": "string",
282
+ "description": "Option value a dropdown doneField is set back to when the circle is cleared (ignored for a bool doneField)"
283
+ },
284
+ "groupByField": {
285
+ "type": "string",
286
+ "description": "Dropdown, reference or user column the rows are grouped by"
287
+ },
288
+ "groupByOptions": {
289
+ "type": "array",
290
+ "items": { "type": "string" },
291
+ "description": "Columns the user may regroup by; must include groupByField"
292
+ },
293
+ "collapsedGroups": {
294
+ "type": "array",
295
+ "items": { "type": "string" },
296
+ "description": "Option values of groupByField whose groups start collapsed"
297
+ },
298
+ "inlineAdd": {
299
+ "type": "boolean",
300
+ "description": "Show an add row in each group (at the top when ungrouped)"
301
+ }
302
+ },
303
+ "additionalProperties": false
304
+ },
252
305
  "listLevelConfig": {
253
306
  "type": "object",
254
307
  "required": ["entityCode", "foreignKey"],
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "entities": {
20
20
  "type": "object",
21
- "description": "Consumed provider entities, keyed by entity code (e.g. 'party'). At least one, except against a system module (admin, metadata, workspace, sys-billing), where {} declares a pure platform-version gate for a feature that introduced no entity, or when 'functions' imports at least one function.",
21
+ "description": "Consumed provider entities, keyed by entity code (e.g. 'party'). At least one, except against a system module (admin, metadata, workspace, sys-billing), where {} declares a pure platform-version gate for a feature that introduced no entity, or when 'functions' imports at least one function or 'snippets' lists at least one print snippet.",
22
22
  "additionalProperties": { "$ref": "#/definitions/entityContract" }
23
23
  },
24
24
  "functions": {
@@ -27,13 +27,21 @@
27
27
  "items": { "type": "string", "pattern": "^[A-Za-z_$][A-Za-z0-9_$]*$" },
28
28
  "uniqueItems": true,
29
29
  "minItems": 1
30
+ },
31
+ "snippets": {
32
+ "type": "array",
33
+ "description": "The provider's print snippets (type 'snippet' in its ui/print_templates.json) the consumer's print templates include as {% include '<provider>.<snippet>' %}. Every such include must be listed, and install checks the installed provider ships each. See docs/modules/dependency-contracts.md.",
34
+ "items": { "type": "string", "pattern": "^[^.\\s]+$" },
35
+ "uniqueItems": true,
36
+ "minItems": 1
30
37
  }
31
38
  },
32
39
  "if": {
33
40
  "not": {
34
41
  "anyOf": [
35
42
  { "properties": { "module": { "enum": ["admin", "metadata", "workspace", "sys-billing"] } }, "required": ["module"] },
36
- { "required": ["functions"] }
43
+ { "required": ["functions"] },
44
+ { "required": ["snippets"] }
37
45
  ]
38
46
  }
39
47
  },
@@ -602,6 +602,25 @@
602
602
  "type": "string",
603
603
  "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."
604
604
  },
605
+ "fieldOptions": {
606
+ "type": "object",
607
+ "description": "Per-record rules for this field, each a formula over the record evaluated in the browser wherever the value renders (grid, card, list, checklist, kanban). $value stands for this column, so one rule (e.g. on a column domain) fits every column using it: IF($value < 0, 'danger', ''). Install refuses a rule that does not parse or calls an unknown function. Not-equal is != (there is no <>).",
608
+ "properties": {
609
+ "foreColor": {
610
+ "type": "string",
611
+ "description": "Formula returning the text colour, or '' for none. A theme name (danger, warning, success, info, accent, muted) follows light/dark mode and the UI style; any other value is used as CSS. E.g. IF([due_date] < TODAY() AND [status] != 'done', 'danger', '')"
612
+ },
613
+ "backColor": {
614
+ "type": "string",
615
+ "description": "Formula returning the background colour, or '' for none. A theme name gives that colour's subtle tint."
616
+ },
617
+ "canEdit": {
618
+ "type": "string",
619
+ "description": "Formula; false makes the field read-only on that record (a UI hint — the server enforces its own locks)."
620
+ }
621
+ },
622
+ "additionalProperties": false
623
+ },
605
624
  "patternFlags": {
606
625
  "type": "string",
607
626
  "description": "Flags for 'pattern', as a JavaScript regex literal would carry them; 'i' and 'm' are the two honoured, each at most once — the RegExp constructor rejects a repeated flag, and a rule the browser cannot compile is skipped there and would then be enforced by the API alone. JavaScript has no inline (?i), so case-insensitivity has to be stated here.",
@@ -0,0 +1,144 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "dForge Module Test Scenarios",
4
+ "description": "Scenarios for a dForge module (tests/*.test.json), run by `dforge-cli module test`. Covers the v0 subset described in docs/modules/module-tests.md: given / do / expect over the API as the signed-in user.",
5
+ "type": "object",
6
+ "required": ["describe", "tests"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "$comment": { "type": "string" },
10
+ "describe": { "type": "string", "description": "Name of the suite." },
11
+ "fixture": { "type": "string", "description": "Named fixture from tests/test.json. Not read by v0: the workspace must already hold the data the scenarios rely on." },
12
+ "isolation": { "enum": ["rollback", "clone"], "description": "Not used by v0: tests commit and leave their records behind." },
13
+ "tests": { "type": "array", "items": { "$ref": "#/$defs/test" } }
14
+ },
15
+ "$defs": {
16
+ "test": {
17
+ "type": "object",
18
+ "required": ["name"],
19
+ "additionalProperties": false,
20
+ "properties": {
21
+ "$comment": { "type": "string" },
22
+ "name": { "type": "string" },
23
+ "as": { "const": "admin", "description": "v0 runs as the signed-in user; other fixture users are not supported yet." },
24
+ "given": { "type": "array", "items": { "$ref": "#/$defs/givenBlock" } },
25
+ "do": { "type": "array", "items": { "$ref": "#/$defs/step" } },
26
+ "expect": { "type": "array", "items": { "$ref": "#/$defs/expectation" } }
27
+ }
28
+ },
29
+ "givenBlock": {
30
+ "type": "object",
31
+ "required": ["entityCode", "records"],
32
+ "additionalProperties": false,
33
+ "properties": {
34
+ "entityCode": { "type": "string" },
35
+ "records": { "type": "array", "items": { "$ref": "#/$defs/values" } }
36
+ }
37
+ },
38
+ "values": {
39
+ "type": "object",
40
+ "description": "Column values. A string \"$name.column\" reads a column of a record named earlier with \"$as\". \"$as\" names the record being created.",
41
+ "properties": { "$as": { "type": "string" } }
42
+ },
43
+ "name": { "type": "string", "pattern": "^\\$[A-Za-z_][A-Za-z0-9_]*$" },
44
+ "recordRef": {
45
+ "description": "\"$name\" (a record named with \"$as\"), or { \"<entity>\": { \"<column>\": <value>, … } } matching exactly one record.",
46
+ "oneOf": [
47
+ { "$ref": "#/$defs/name" },
48
+ {
49
+ "type": "object",
50
+ "minProperties": 1,
51
+ "maxProperties": 1,
52
+ "additionalProperties": { "type": "object", "minProperties": 1 }
53
+ }
54
+ ]
55
+ },
56
+ "step": {
57
+ "oneOf": [
58
+ {
59
+ "type": "object",
60
+ "required": ["insert"],
61
+ "additionalProperties": false,
62
+ "properties": {
63
+ "insert": { "type": "string", "description": "Entity code." },
64
+ "values": { "$ref": "#/$defs/values" },
65
+ "$as": { "type": "string" }
66
+ }
67
+ },
68
+ {
69
+ "type": "object",
70
+ "required": ["update", "values"],
71
+ "additionalProperties": false,
72
+ "properties": {
73
+ "update": { "$ref": "#/$defs/recordRef" },
74
+ "values": { "$ref": "#/$defs/values" }
75
+ }
76
+ },
77
+ {
78
+ "type": "object",
79
+ "required": ["delete"],
80
+ "additionalProperties": false,
81
+ "properties": { "delete": { "$ref": "#/$defs/recordRef" } }
82
+ },
83
+ {
84
+ "type": "object",
85
+ "required": ["action"],
86
+ "oneOf": [{ "required": ["record"] }, { "required": ["records"] }],
87
+ "additionalProperties": false,
88
+ "properties": {
89
+ "action": { "type": "string", "description": "action_cd." },
90
+ "record": { "$ref": "#/$defs/recordRef" },
91
+ "records": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/recordRef" } },
92
+ "params": { "type": "object" }
93
+ }
94
+ }
95
+ ]
96
+ },
97
+ "expectation": {
98
+ "oneOf": [
99
+ {
100
+ "type": "object",
101
+ "required": ["record", "equals"],
102
+ "additionalProperties": false,
103
+ "properties": {
104
+ "record": { "$ref": "#/$defs/recordRef" },
105
+ "equals": { "type": "object", "minProperties": 1 },
106
+ "precision": { "type": "integer", "minimum": 0, "description": "Decimals numbers are compared at." }
107
+ }
108
+ },
109
+ {
110
+ "type": "object",
111
+ "required": ["count", "equals"],
112
+ "additionalProperties": false,
113
+ "properties": {
114
+ "count": { "type": "string", "description": "Entity code." },
115
+ "where": { "type": "object", "description": "Canonical { c, o, v } / { g, i } filter." },
116
+ "equals": { "type": "integer", "minimum": 0 }
117
+ }
118
+ },
119
+ {
120
+ "type": "object",
121
+ "required": ["error"],
122
+ "additionalProperties": false,
123
+ "properties": {
124
+ "error": {
125
+ "type": "object",
126
+ "minProperties": 1,
127
+ "additionalProperties": false,
128
+ "properties": {
129
+ "code": { "type": "string" },
130
+ "contains": { "type": "string" }
131
+ }
132
+ }
133
+ }
134
+ },
135
+ {
136
+ "type": "object",
137
+ "required": ["refused"],
138
+ "additionalProperties": false,
139
+ "properties": { "refused": { "const": "canExecute" } }
140
+ }
141
+ ]
142
+ }
143
+ }
144
+ }
package/src/data-views.ts CHANGED
@@ -12,6 +12,7 @@ export type { NamedKind };
12
12
  export type ViewType =
13
13
  | "grid"
14
14
  | "list"
15
+ | "checklist"
15
16
  | "kanban"
16
17
  | "calendar"
17
18
  | "gallery"
@@ -24,6 +25,7 @@ export type ViewType =
24
25
  export const dataViewKinds: readonly NamedKind[] = [
25
26
  { cd: "grid", name: "Grid" },
26
27
  { cd: "list", name: "List" },
28
+ { cd: "checklist", name: "Checklist" },
27
29
  { cd: "kanban", name: "Kanban" },
28
30
  { cd: "calendar", name: "Calendar" },
29
31
  { cd: "gallery", name: "Gallery" },
@@ -121,6 +123,27 @@ export interface ListViewConfig {
121
123
  inlineAdd?: boolean;
122
124
  }
123
125
 
126
+ export interface ChecklistViewConfig {
127
+ /** Column of the row title; defaults to the record's display name. Also
128
+ * the column an inline add row types into. */
129
+ titleField?: string;
130
+ /** Column the row's done circle reads and writes: a bool column, or a
131
+ * dropdown column together with `doneValue`. */
132
+ doneField: string;
133
+ /** Option value of a dropdown `doneField` that means done. */
134
+ doneValue?: string;
135
+ /** Option value a dropdown `doneField` is set back to when the circle is cleared. */
136
+ undoneValue?: string;
137
+ /** Dropdown, reference or user column the rows are grouped by. */
138
+ groupByField?: string;
139
+ /** Columns the user may regroup by; includes `groupByField`. */
140
+ groupByOptions?: string[];
141
+ /** Option values of `groupByField` whose groups start collapsed. */
142
+ collapsedGroups?: string[];
143
+ /** Show an add row in each group (at the top when ungrouped). */
144
+ inlineAdd?: boolean;
145
+ }
146
+
124
147
  export interface DiagramViewConfig {
125
148
  /** Column to group diagram nodes by. */
126
149
  groupByField?: string;
@@ -149,6 +172,7 @@ export interface DataViewDef {
149
172
  | CalendarViewConfig
150
173
  | TreeGridViewConfig
151
174
  | ListViewConfig
175
+ | ChecklistViewConfig
152
176
  | DiagramViewConfig
153
177
  | Record<string, unknown>;
154
178
  }
package/src/deps.ts CHANGED
@@ -85,4 +85,14 @@ export interface DepsFile {
85
85
  version: string;
86
86
  /** Consumed provider entities, keyed by entity code. */
87
87
  entities: Record<string, DepEntity>;
88
+ /**
89
+ * The provider's DSL library functions the consumer calls as
90
+ * `<provider code with '-' replaced by '_'>.<name>(…)`.
91
+ */
92
+ functions?: string[];
93
+ /**
94
+ * The provider's print snippets the consumer's print templates include as
95
+ * `{% include '<provider>.<snippet>' %}`. Every such include must be listed.
96
+ */
97
+ snippets?: string[];
88
98
  }
@@ -149,9 +149,9 @@ export const BUILTINS: Builtin[] = [
149
149
  },
150
150
  {
151
151
  name: "sendEmail",
152
- signature: "sendEmail(to, subjectOrTemplate, dataOrBody?, attachments?)",
152
+ signature: "sendEmail(to, subjectOrTemplate, dataOrBody?, attachments?, options?)",
153
153
  returns: "void",
154
- doc: "Queue an outbound email. Three arguments, and the **second** picks the mode: `sendEmail(to, 'order.shipped', { ref: [code] })` sends a template, `sendEmail(to, 'Your order shipped', '<p>…</p>')` sends raw HTML. A second argument with no spaces that contains `.` or `_` is read as a template code — so a raw subject like `'Shipped.'` silently becomes one; give a raw subject spaces. The optional **fourth** argument attaches files: a `printPdf()`, `attachText()` or `attachBase64()` result, or an array of them (`[a, b]`; pass a single file as itself, because a one-name `[a]` reads as a field reference in the DSL). A `printPdf()` file can be attached only by the user who generated it (or a workspace admin) and not in a scheduled job; a file built with `attachText()` / `attachBase64()` has no owner and works everywhere, a scheduled job included. At most 5 files and 10 MB in all per email (`Email:MaxAttachments`, `Email:MaxAttachmentBytes`). A file the sender may not attach fails the action.",
154
+ doc: "Queue an outbound email. Three arguments, and the **second** picks the mode: `sendEmail(to, 'order.shipped', { ref: [code] })` sends a template, `sendEmail(to, 'Your order shipped', '<p>…</p>')` sends raw HTML. A second argument with no spaces that contains `.` or `_` is read as a template code — so a raw subject like `'Shipped.'` silently becomes one; give a raw subject spaces. The optional **fourth** argument attaches files: a `printPdf()`, `attachText()` or `attachBase64()` result, or an array of them (`[a, b]`; pass a single file as itself, because a one-name `[a]` reads as a field reference in the DSL). A `printPdf()` file can be attached only by the user who generated it (or a workspace admin) and not in a scheduled job; a file built with `attachText()` / `attachBase64()` has no owner and works everywhere, a scheduled job included. At most 5 files and 10 MB in all per email (`Email:MaxAttachments`, `Email:MaxAttachmentBytes`). A file the sender may not attach fails the action. The optional **fifth** argument sets threading headers so a reply finds this mail: `{ messageId, inReplyTo, references, replyTo }` (ids with angle brackets, `references` an array; pass `null` as the fourth argument when there are no files). Every key is optional; an unknown key, a malformed id (strictly `<word.word@word.word>`) or address, or a line break fails the action by name.",
155
155
  },
156
156
 
157
157
  // External calls
@@ -161,6 +161,12 @@ export const BUILTINS: Builtin[] = [
161
161
  returns: "string",
162
162
  doc: "HTTP request to an external API; returns the body as a string. Max 10 calls per script, 120s timeout, 5MB response.",
163
163
  },
164
+ {
165
+ name: "fetchMail",
166
+ signature: "fetchMail(options)",
167
+ returns: "object",
168
+ doc: "Read new mail from one IMAP folder, read-only: nothing is marked seen, moved or deleted, so the mailbox stays the user's and the cursor you store says what was imported. Options: `host`, `username`, `secret` (the code of a module secret — the password is never written in a script), `port` (993; 143 with `security: 'starttls'`), `security` (`'ssl'` by default or `'starttls'`), `auth` (`'password'`, the only one for now), `folder` (`'INBOX'`), the cursor `uidValidity` + `afterUid` from the previous result, `since` (a date, for the first call), `limit` (messages). Returns `{ uidValidity, lastUid, more, reset, messages }`: store `uidValidity` and `lastUid` for the next call, call again while `more` is true, and treat `reset: true` as a mailbox renumbered by the server (the cursor was dropped and the folder read again). Each message has `uid`, `messageId`, `inReplyTo`, `references`, `date` (ISO 8601), `from`/`replyTo` (`{ address, name }`), `to`, `cc`, `envelopeTo`, `subject`, `text`, `html` (sanitized; remote images parked in `data-remote-src`), `autoReply`, `attachments` and `droppedAttachments`. Each attachment is a staged file `{ $tempId, fileName, mimeType, size, checksum, contentId, inline }`: pass it as the value of a `file` column in `insert()` / `update()` to keep it (an unused one expires with the temp sweep). Failures throw with the code first: `AUTH_FAILED`, `CONNECT_FAILED`, `TLS_FAILED`, `FOLDER_NOT_FOUND`; a host on a private network needs `Dsl:Limits:AllowedHosts`. Meant for scheduled jobs and async actions: up to 3 calls per run, 25 messages and 40 s per call (`Dsl:Limits:MaxMailFetchesPerExecution` / `MaxMailMessagesPerFetch` / `MailFetchBudgetSeconds`).",
169
+ },
164
170
  {
165
171
  name: "callService",
166
172
  signature: "callService(name, args)",
package/src/index.ts CHANGED
@@ -95,6 +95,7 @@ export {
95
95
  type TreeGridViewConfig,
96
96
  type ListViewConfig,
97
97
  type ListLevelConfig,
98
+ type ChecklistViewConfig,
98
99
  type DiagramViewConfig,
99
100
  } from "./data-views";
100
101