@xeplr/actions 1.2.0 → 1.3.1

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/README.md CHANGED
@@ -87,7 +87,7 @@ var moved = await actions.runAction(actions.builtins.dbMove, {
87
87
  |---|---|
88
88
  | `name` | Required. |
89
89
  | `description` | Text for pickers. |
90
- | `inputSchema` | `[{ name, type, required, default, description, order, ... }]`, validated by `@xeplr/schema-handler` `applySchema`. Built-ins also use `group`, `showWhen`, `system`, `options`, `optionsFrom`, `dependsOn`, `label`, `hidden` (left out of a form unless a value is already set) and `control` (`'parameters'`: `[{ name, value }]` shown as rows, a value fixed or bound to the run's window: `{window.from}` / `{window.to}`, see `@xeplr/jobs`) — hints for a form; validation treats every field the same. |
90
+ | `inputSchema` | `[{ name, type, required, default, description, order, ... }]`, validated by `@xeplr/schema-handler` `applySchema`. Built-ins also use `group`, `showWhen`, `system`, `options`, `optionsFrom`, `dependsOn`, `label`, `hidden` (left out of a form unless a value is already set), `suggestFrom` / `suggestWith` (a host option source that suggests values, asked with the named sibling inputs; `db-procedure`'s parameters ask `procedureParams` with the connection, database and procedure) and `control` (`'parameters'`: `[{ name, value }]` shown as rows, a value fixed or bound to the run's window: `{window.from}` / `{window.to}`, see `@xeplr/jobs`) — hints for a form; validation treats every field the same. |
91
91
  | `outputSchema` | Optional documentation of the return value. |
92
92
  | `requires` | Module names checked with `require.resolve` at `register()`. |
93
93
  | `execute({ input, system, metaStore })` | Returns (or resolves to) the output. |
@@ -260,6 +260,8 @@ await runAction(builtins.exportTable, {
260
260
  - `rect` (`fill`, `stroke`, `radius`)
261
261
  - `line`
262
262
 
263
+ **`page: { width, height }`** (points, 72–14,400 each) makes every page exactly that size, edge to edge: no margins, no title and no footer or page numbers. It is for a PDF *of the screen*: a board captured as one image, posted as a single `image` block at `x: 0, y: 0, w: 1, h: 1` on a page the board's own size. Nothing is scaled, so what you see is what you get. Without `page`, the layout is A4 with margins, the title on page 1 and a footer on each page, as before.
264
+
263
265
  What it is given is assumed to come from a browser, so it is bounded: 50 pages, 200 blocks a page, 8 MB per image, 500 table rows, and 5,000 characters of text. An image that will not decode draws a note in its box instead of failing the whole file.
264
266
 
265
267
  Peers (optional, needed only by the format used): `fflate` for xlsx; `pdfkit` and `dejavu-fonts-ttf` for pdf. `writers` (`getWriter`, `INFO`, `SUPPORTED`) is exported for code that wants a writer without the action.
@@ -101,7 +101,12 @@ module.exports = {
101
101
  // `control: 'parameters'`: a form shows these as rows — a name and where
102
102
  // its value comes from (a fixed value, or the start / end of the run's
103
103
  // window, which a job binds as "{window.from}" / "{window.to}").
104
+ // `suggestFrom`: where a form finds the procedure's own parameter names
105
+ // (a host option source), asked with the values of `suggestWith` — the
106
+ // connection, database and procedure chosen above.
104
107
  { name: 'params', type: 'array', order: 7, control: 'parameters', label: 'Parameters',
108
+ suggestFrom: 'procedureParams',
109
+ suggestWith: { connectionId: 'connectionInfoId', dbInfoId: 'dbInfoId', procedure: 'procedure' },
105
110
  description: 'Parameters, as [{ name, value }] or bare values. Named entries are matched by ' +
106
111
  'name on postgres and mssql; MySQL has no named-argument syntax, so there they ' +
107
112
  'are positional and must be in the order the procedure declares them.' },
@@ -25,11 +25,22 @@ function fail(message) {
25
25
  return err;
26
26
  }
27
27
 
28
+ // A page of an exact size, in points: at least an inch, at most PDF's own
29
+ // 200-inch limit (14,400 pt) on a side.
30
+ function pageSize(page) {
31
+ if (page == null) return;
32
+ var ok = function(n) { return typeof n === 'number' && isFinite(n) && n >= 72 && n <= 14400; };
33
+ if (typeof page !== 'object' || !ok(page.width) || !ok(page.height)) {
34
+ throw fail('page must be { width, height } in points, each between 72 and 14400');
35
+ }
36
+ }
37
+
28
38
  function fraction(n) { return typeof n === 'number' && isFinite(n) && n >= -0.01 && n <= 50; }
29
39
 
30
40
  /** Throws (400) naming the first block that is not one this action draws. */
31
41
  function validate(spec) {
32
42
  var pages = spec.pages;
43
+ pageSize(spec.page);
33
44
  if (!Array.isArray(pages) || !pages.length) throw fail('pages must list at least one page');
34
45
  if (pages.length > LIMITS.pages) throw fail('At most ' + LIMITS.pages + ' pages');
35
46
  pages.forEach(function(page, p) {
@@ -66,7 +77,9 @@ module.exports = {
66
77
  { name: 'title', type: 'string', order: 2 },
67
78
  { name: 'footer', type: 'string', order: 3 },
68
79
  { name: 'landscape', type: 'boolean', order: 4, description: 'Default true.' },
69
- { name: 'outputPath', type: 'string', order: 5 }
80
+ { name: 'outputPath', type: 'string', order: 5 },
81
+ { name: 'page', type: 'object', order: 6,
82
+ description: '{ width, height } in points: every page exactly that size, edge to edge, no title or footer. For a PDF of what the screen shows.' }
70
83
  ],
71
84
  outputSchema: [
72
85
  { name: 'filePath', type: 'string' }, { name: 'fileName', type: 'string' },
@@ -82,7 +95,8 @@ module.exports = {
82
95
  fs.mkdirSync(dir, { recursive: true });
83
96
  var filePath = input.outputPath || path.join(fs.mkdtempSync(path.join(dir, 'export-')), name);
84
97
  var done = await pdf.renderPages(filePath, {
85
- pages: input.pages, title: input.title, footer: input.footer, landscape: input.landscape !== false
98
+ pages: input.pages, title: input.title, footer: input.footer, landscape: input.landscape !== false,
99
+ page: input.page || null
86
100
  });
87
101
  return {
88
102
  filePath: filePath, fileName: path.basename(filePath), contentType: 'application/pdf',
@@ -28,8 +28,8 @@ function newDocument(filePath, opts) {
28
28
  var PDFDocument = require('pdfkit');
29
29
  var doc = new PDFDocument({
30
30
  size: opts.size || 'A4',
31
- layout: opts.landscape ? 'landscape' : 'portrait',
32
- margins: { top: 36, bottom: 36, left: 32, right: 32 },
31
+ layout: opts.size ? 'portrait' : (opts.landscape ? 'landscape' : 'portrait'),
32
+ margins: opts.margin === 0 ? { top: 0, bottom: 0, left: 0, right: 0 } : { top: 36, bottom: 36, left: 32, right: 32 },
33
33
  bufferPages: true,
34
34
  info: { Title: opts.title || 'Export', Producer: '@xeplr/actions' }
35
35
  });
@@ -222,7 +222,13 @@ module.exports = {
222
222
  * @param {object} spec - { pages: [{ blocks }], title?, footer?, landscape?: true, font?, boldFont? }
223
223
  */
224
224
  renderPages: async function(filePath, spec) {
225
- var made = newDocument(filePath, { title: spec.title, landscape: spec.landscape !== false, font: spec.font, boldFont: spec.boldFont });
225
+ // spec.page — { width, height } in points: a page exactly that size, edge
226
+ // to edge, with no title and no footer. What a "PDF of the screen" needs:
227
+ // the content IS the page, so nothing is scaled, margined or stamped.
228
+ var exact = spec.page && spec.page.width && spec.page.height ? spec.page : null;
229
+ var made = newDocument(filePath, exact
230
+ ? { title: spec.title, size: [exact.width, exact.height], margin: 0, font: spec.font, boldFont: spec.boldFont }
231
+ : { title: spec.title, landscape: spec.landscape !== false, font: spec.font, boldFont: spec.boldFont });
226
232
  var pdf = made.doc;
227
233
  var pages = spec.pages && spec.pages.length ? spec.pages : [{ blocks: [] }];
228
234
  try {
@@ -230,8 +236,10 @@ module.exports = {
230
236
  pages.forEach(function(page, p) {
231
237
  if (p > 0) pdf.addPage();
232
238
  var m = pdf.page.margins;
233
- var box = { x: m.left, y: m.top, w: pdf.page.width - m.left - m.right, h: pdf.page.height - m.top - m.bottom - 14 };
234
- if (p === 0 && spec.title) {
239
+ var box = exact
240
+ ? { x: 0, y: 0, w: pdf.page.width, h: pdf.page.height }
241
+ : { x: m.left, y: m.top, w: pdf.page.width - m.left - m.right, h: pdf.page.height - m.top - m.bottom - 14 };
242
+ if (p === 0 && spec.title && !exact) {
235
243
  pdf.font('bold').fontSize(13).fillColor(COLORS.text).text(spec.title, box.x, box.y, { width: box.w, lineBreak: false });
236
244
  var used = pdf.y - box.y + 6;
237
245
  box = { x: box.x, y: box.y + used, w: box.w, h: box.h - used };
@@ -281,7 +289,7 @@ module.exports = {
281
289
  pdf.restore();
282
290
  });
283
291
  });
284
- stampPages(pdf, spec.footer);
292
+ if (!exact) stampPages(pdf, spec.footer);
285
293
  } catch (err) {
286
294
  made.abandon();
287
295
  throw err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xeplr/actions",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Named-action registry with typed inputs and safe invocation — plus built-in actions for HTTP, DB, files, email, spawn",
5
5
  "main": "index.js",
6
6
  "files": [