@dforge-core/metadata 0.0.43 → 0.0.45

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.43",
3
+ "version": "0.0.45",
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",
@@ -108,10 +108,20 @@
108
108
  },
109
109
  "traits": {
110
110
  "type": "array",
111
- "description": "Entity traits expanded during module install (authoring shortcut). Trait definitions loaded from DB (seeded by metadata module).",
111
+ "description": "Entity traits expanded during module install (authoring shortcut): a platform trait, or one the module declares in its own traits.json. Pack and install refuse a name that is neither; the schema cannot see traits.json, so it accepts any trait-shaped name and lists only the platform traits.",
112
112
  "items": {
113
- "type": "string",
114
- "enum": ["identity", "audit", "audit-full", "soft-delete", "sorting", "postable", "accumulation", "ledger", "period", "file-library"]
113
+ "anyOf": [
114
+ {
115
+ "type": "string",
116
+ "enum": ["identity", "audit", "audit-full", "soft-delete", "sorting", "postable", "accumulation", "ledger", "period", "file-library"],
117
+ "description": "Platform trait."
118
+ },
119
+ {
120
+ "type": "string",
121
+ "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$",
122
+ "description": "A trait declared in the module's own traits.json."
123
+ }
124
+ ]
115
125
  },
116
126
  "uniqueItems": true
117
127
  },
@@ -418,7 +428,7 @@
418
428
  "fieldTypeCd": {
419
429
  "type": "string",
420
430
  "enum": [
421
- "text", "avatar", "email", "phone", "url", "textarea", "number", "currency", "percent",
431
+ "text", "avatar", "email", "phone", "url", "textarea", "number", "currency", "percent", "filesize",
422
432
  "checkbox", "date", "datetime", "time",
423
433
  "dropdown", "flags", "tags", "json", "hidden", "color", "image", "file",
424
434
  "markdown", "richtext", "lookup", "user", "grid", "list", "entitylink", "code"
@@ -401,9 +401,13 @@
401
401
 
402
402
  "columnDef": {
403
403
  "type": "object",
404
+ "description": "Override of one column the dataset already projects (from the entity, the stored procedure's result columns, or the register). Name only what differs: each key replaces the projected value. A label here is a literal and wins over a translated header.",
404
405
  "properties": {
405
- "label": { "type": "string" },
406
- "visible": { "type": "boolean" },
406
+ "label": { "type": "string", "description": "Column header. Not translated: leave it out to keep the entity's or the procedure's translated header." },
407
+ "fieldTypeCd": { "type": "string", "description": "Control the value renders with, replacing the projected one." },
408
+ "format": { "type": "string", "description": "Display format, replacing the column's display format." },
409
+ "baseDatatypeCd": { "type": "string", "description": "Read only where the dataset projects no columns of its own (a stored procedure with no result columns), when columnsDef is the column list. Ignored as an override: the projected column's datatype stands." },
410
+ "visible": { "type": "boolean", "description": "Accepted, but neither report.run nor the client reads it: it has no effect." },
407
411
  "width": { "type": "integer", "minimum": 1 }
408
412
  },
409
413
  "additionalProperties": false
@@ -84,6 +84,23 @@
84
84
  "$ref": "#/$defs/report"
85
85
  }
86
86
  },
87
+ "storedProcedures": {
88
+ "type": "object",
89
+ "description": "Stored-procedure code (logic/stored_procedures.json) → result-column headers. A header belongs to the procedure, not to a report: every dataset bound to the procedure shows the same translation. A dataset's columnsDef label overrides it with a literal, so leave the label out of columnsDef to keep the column translated. Required, per declared column, by `module pack` / `module validate` for each non-English supported locale; install only warns.",
90
+ "additionalProperties": {
91
+ "type": "object",
92
+ "additionalProperties": false,
93
+ "properties": {
94
+ "columns": {
95
+ "type": "object",
96
+ "description": "Result column code → header.",
97
+ "additionalProperties": {
98
+ "$ref": "#/$defs/labelOnly"
99
+ }
100
+ }
101
+ }
102
+ }
103
+ },
87
104
  "actions": {
88
105
  "type": "object",
89
106
  "description": "Action code (ui/actions.json) → label, tooltip and parameter labels.",
@@ -248,6 +265,10 @@
248
265
  "label": {
249
266
  "$ref": "#/$defs/label"
250
267
  },
268
+ "desc": {
269
+ "type": "string",
270
+ "description": "Translated report description."
271
+ },
251
272
  "datasets": {
252
273
  "type": "object",
253
274
  "description": "Dataset code → caption.",
package/src/actions.ts CHANGED
@@ -60,6 +60,25 @@ export interface ActionDef {
60
60
  * "Run in Background". A parameterless `isAsync` action is queued directly.
61
61
  */
62
62
  isAsync?: boolean;
63
+ /**
64
+ * Code of a print template of the action's entity. The run dialog renders it as a PDF beside the params when the
65
+ * action is opened on one record, so the user sees the document the action sends before it goes.
66
+ */
67
+ previewTemplate?: string;
68
+ /**
69
+ * Saved parameter sets the module ships for this action, keyed by set name. They install as shared sets owned by
70
+ * the module: read-only for users and refreshed on every install, and a user can save a set of their own under the
71
+ * same name, which wins for them. Each `values` key must be a param the action declares; a string starting with `=`
72
+ * is a formula. Needs the action to declare params.
73
+ */
74
+ paramSets?: Record<string, ActionParamSetDef>;
75
+ }
76
+
77
+ /** One shipped parameter set of an action. */
78
+ export interface ActionParamSetDef {
79
+ description?: string;
80
+ /** Value by param code. */
81
+ values: Record<string, unknown>;
63
82
  }
64
83
 
65
84
  /** A `ui/actions.json` file: action code → definition. */
package/src/derive.ts CHANGED
@@ -24,6 +24,7 @@ const TEXT_BACKED = new Set(["textarea", "markdown", "richtext", "code"]);
24
24
  const DB_DATATYPE_OVERRIDE: Record<string, string> = {
25
25
  file: "jsonb",
26
26
  image: "jsonb",
27
+ filesize: "int8",
27
28
  };
28
29
 
29
30
  export interface DeriveOptions {
@@ -149,9 +149,9 @@ export const BUILTINS: Builtin[] = [
149
149
  },
150
150
  {
151
151
  name: "sendEmail",
152
- signature: "sendEmail(to, subjectOrTemplate, dataOrBody?)",
152
+ signature: "sendEmail(to, subjectOrTemplate, dataOrBody?, attachments?)",
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. There is no fourth argument.",
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.",
155
155
  },
156
156
 
157
157
  // External calls
@@ -192,7 +192,31 @@ export const BUILTINS: Builtin[] = [
192
192
  name: "download",
193
193
  signature: "download(url, fileName?)",
194
194
  returns: "void",
195
- doc: "Set a download URL on the action result; the browser opens it when the action returns. It fetches nothing and stores nothing — the URL must already serve the bytes, e.g. the `/api/temp-file/{token}` a service handed back. To put a remote file INTO storage, fetch it with `callApi()` and write it to a file column.",
195
+ doc: "Set a download URL on the action result; the browser opens it when the action returns. It fetches nothing and stores nothing — the URL must already serve the bytes, e.g. the `/api/temp-file/{token}` a service handed back. To hand over a file the script builds, use `downloadText()` / `downloadBase64()`. To put a remote file INTO storage, fetch it with `callApi()` and write it to a file column.",
196
+ },
197
+ {
198
+ name: "downloadText",
199
+ signature: "downloadText(content, fileName, encoding?, mimeType?)",
200
+ returns: "string",
201
+ doc: "Hand the user a text file the script built: encodes `content` in `encoding` (`utf-8` by default; `windows-1251`, `cp866`, `koi8-u`… also work), stores it in temp storage and sets it as the action's download. Line endings are written as given (join with `'\\r\\n'` for CR LF), no BOM. Returns the signed `/api/temp-file/{token}` URL, valid for an hour (`FileStorage:TempTokenExpirationMinutes`). An action has one download: a later call replaces the earlier. In a background or scheduled run nobody receives the download, so pass the URL on in `notify()`. Max 10 MB per file, 10 files per run.",
202
+ },
203
+ {
204
+ name: "downloadBase64",
205
+ signature: "downloadBase64(base64, fileName, mimeType?)",
206
+ returns: "string",
207
+ doc: "`downloadText()` for bytes: decodes `base64` and hands it to the user as `fileName` (`application/octet-stream` unless `mimeType` says otherwise). Returns the signed download URL.",
208
+ },
209
+ {
210
+ name: "attachText",
211
+ signature: "attachText(content, fileName, encoding?, mimeType?)",
212
+ returns: "object",
213
+ doc: "Build a text file to attach to an email: encodes `content` in `encoding` (`utf-8` by default) and returns a descriptor for `sendEmail()`'s fourth argument, e.g. `sendEmail(to, 'Interview', body, attachText(ics, 'invite.ics', 'utf-8', 'text/calendar'))`. The file keeps `mimeType` (`text/plain` by default). It is the script's own output, so no user's rights are involved and it works in a scheduled job. Only the emails of this run can name it. Max 10 MB per file and 10 files per run, shared with `downloadText()` / `downloadBase64()`.",
214
+ },
215
+ {
216
+ name: "attachBase64",
217
+ signature: "attachBase64(base64, fileName, mimeType?)",
218
+ returns: "object",
219
+ doc: "`attachText()` for bytes given as base64 (`application/octet-stream` unless `mimeType` says otherwise).",
196
220
  },
197
221
  {
198
222
  name: "getSetting",
@@ -247,6 +271,13 @@ export const BUILTINS: Builtin[] = [
247
271
  entityArg: 0,
248
272
  doc: "Build a clickable link to a record, for an `entitylink` column. Always pass a **qualified** code — `entity_cd` is only unique per module.",
249
273
  },
274
+ {
275
+ name: "printPdf",
276
+ signature: "printPdf(entityCd, key, templateCd)",
277
+ returns: "object",
278
+ entityArg: 0,
279
+ doc: "Render a print template to a PDF **as the current user** and return the stored file as `{ fileId, fileName, mimeType, size, checksum }` — a descriptor, never the bytes. `key` is the record's `{ column: value }` key (or a scalar for a single-column key); `templateCd` is the template's code on that entity. The record is read under the user's rights and the folder's row filter, as the Generate PDF button reads it. Always pass a **qualified** entity code. Not available in a scheduled job or a background action (no signed-in user), refused in a computing-only run, and limited to 10 per execution; each render holds the action's transaction open while it works.",
280
+ },
250
281
  ];
251
282
 
252
283
  export const BUILTIN_BY_NAME = new Map(BUILTINS.map((b) => [b.name, b]));
@@ -20,6 +20,7 @@ export const fieldTypes = [
20
20
  { cd: "number", name: "Number", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: null, useStringInFilter: false },
21
21
  { cd: "currency", name: "Currency", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: 2, useStringInFilter: false },
22
22
  { cd: "percent", name: "Percent", columnType: "D", baseDatatypeCd: "number", supportedAgg: "CAM", maxLen: null, precision: 2, useStringInFilter: false },
23
+ { cd: "filesize", name: "File Size", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: null, useStringInFilter: false },
23
24
  { cd: "checkbox", name: "Checkbox", columnType: "D", baseDatatypeCd: "bool", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: false },
24
25
  { cd: "date", name: "Date", columnType: "D", baseDatatypeCd: "date", supportedAgg: "CM", maxLen: null, precision: null, useStringInFilter: false },
25
26
  { cd: "datetime", name: "Date & Time", columnType: "D", baseDatatypeCd: "timestamp", supportedAgg: "CM", maxLen: null, precision: null, useStringInFilter: false },
package/src/index.ts CHANGED
@@ -185,7 +185,7 @@ export type {
185
185
  TabGroupSection,
186
186
  } from "./card-layouts";
187
187
  export type { SeedDataFile, SeedDataRepeat } from "./seed-data";
188
- export type { ActionsFile, ActionDef, ActionExecutionMode } from "./actions";
188
+ export type { ActionsFile, ActionDef, ActionParamSetDef, ActionExecutionMode } from "./actions";
189
189
 
190
190
  // Flag catalog — the only letters valid on an authored column, at field level and
191
191
  // on a per-view override alike (both `flags` patterns in entity.schema.json are
package/src/reports.ts CHANGED
@@ -115,7 +115,11 @@ export interface ReportQuery {
115
115
 
116
116
  /** Per-column metadata override on a dataset. */
117
117
  export interface ColumnDef {
118
+ /** A literal header: wins over the entity's or the procedure's translated one. */
118
119
  label?: string;
120
+ fieldTypeCd?: string;
121
+ format?: string;
122
+ /** Accepted, but read by nothing. */
119
123
  visible?: boolean;
120
124
  width?: number;
121
125
  }
package/src/types.ts CHANGED
@@ -50,6 +50,7 @@ export type FieldTypeCd =
50
50
  | "number"
51
51
  | "currency"
52
52
  | "percent"
53
+ | "filesize"
53
54
  | "checkbox"
54
55
  | "date"
55
56
  | "datetime"