@reportwright/engine 0.0.0-stage → 0.11.0

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +206 -0
  2. package/LICENSE +23 -0
  3. package/README.md +67 -2
  4. package/dist/index.js +20267 -0
  5. package/dist/index.js.map +6 -0
  6. package/dist/types/packages/engine/entry.d.ts +75 -0
  7. package/dist/types/src/designer/tableGen.d.ts +119 -0
  8. package/dist/types/src/engine/data/guard.d.ts +50 -0
  9. package/dist/types/src/engine/data/index.d.ts +120 -0
  10. package/dist/types/src/engine/data/odata.d.ts +35 -0
  11. package/dist/types/src/engine/data/xml.d.ts +108 -0
  12. package/dist/types/src/engine/expr/evaluate.d.ts +183 -0
  13. package/dist/types/src/engine/expr/format.d.ts +122 -0
  14. package/dist/types/src/engine/expr/javafmt.d.ts +26 -0
  15. package/dist/types/src/engine/expr/parser.d.ts +29 -0
  16. package/dist/types/src/engine/image.d.ts +107 -0
  17. package/dist/types/src/engine/index.d.ts +224 -0
  18. package/dist/types/src/engine/items/barcode.d.ts +121 -0
  19. package/dist/types/src/engine/items/cells.d.ts +11 -0
  20. package/dist/types/src/engine/items/chart-kit.d.ts +75 -0
  21. package/dist/types/src/engine/items/chart-more.d.ts +52 -0
  22. package/dist/types/src/engine/items/chart-round.d.ts +28 -0
  23. package/dist/types/src/engine/items/chart.d.ts +42 -0
  24. package/dist/types/src/engine/items/hpage.d.ts +33 -0
  25. package/dist/types/src/engine/items/index.d.ts +117 -0
  26. package/dist/types/src/engine/items/map.d.ts +11 -0
  27. package/dist/types/src/engine/items/paint.d.ts +138 -0
  28. package/dist/types/src/engine/items/pivot.d.ts +45 -0
  29. package/dist/types/src/engine/items/rich.d.ts +105 -0
  30. package/dist/types/src/engine/items/toc.d.ts +10 -0
  31. package/dist/types/src/engine/items/visuals.d.ts +5 -0
  32. package/dist/types/src/engine/layout.d.ts +24 -0
  33. package/dist/types/src/engine/paginate/index.d.ts +17 -0
  34. package/dist/types/src/engine/reuse.d.ts +120 -0
  35. package/dist/types/src/engine/schema/report.schema.d.ts +2421 -0
  36. package/dist/types/src/engine/schema/template.d.ts +212 -0
  37. package/dist/types/src/engine/schema/validate.d.ts +21 -0
  38. package/dist/types/src/engine/style.d.ts +70 -0
  39. package/dist/types/src/engine/text/fonts.d.ts +96 -0
  40. package/dist/types/src/engine/text/measure.d.ts +145 -0
  41. package/dist/types/src/engine/text/rich.d.ts +48 -0
  42. package/dist/types/src/engine/text/shaper.d.ts +20 -0
  43. package/dist/types/src/engine/units.d.ts +53 -0
  44. package/dist/types/src/engine/url.d.ts +23 -0
  45. package/dist/types/src/exporters/color.d.ts +9 -0
  46. package/dist/types/src/exporters/csv.d.ts +44 -0
  47. package/dist/types/src/exporters/deadline.d.ts +6 -0
  48. package/dist/types/src/exporters/docx.d.ts +19 -0
  49. package/dist/types/src/exporters/encrypt.d.ts +39 -0
  50. package/dist/types/src/exporters/figures.d.ts +112 -0
  51. package/dist/types/src/exporters/html.d.ts +24 -0
  52. package/dist/types/src/exporters/htmldata.d.ts +32 -0
  53. package/dist/types/src/exporters/pdf.d.ts +45 -0
  54. package/dist/types/src/exporters/pdfa.d.ts +31 -0
  55. package/dist/types/src/exporters/pdfua.d.ts +17 -0
  56. package/dist/types/src/exporters/png.d.ts +10 -0
  57. package/dist/types/src/exporters/pptx.d.ts +14 -0
  58. package/dist/types/src/exporters/regions.d.ts +30 -0
  59. package/dist/types/src/exporters/subset.d.ts +5 -0
  60. package/dist/types/src/exporters/svg.d.ts +26 -0
  61. package/dist/types/src/exporters/svgimage.d.ts +33 -0
  62. package/dist/types/src/exporters/xlsx.d.ts +61 -0
  63. package/dist/types/src/exporters/xlsxchart.d.ts +26 -0
  64. package/dist/types/src/exporters/xlsxwriter.d.ts +83 -0
  65. package/dist/types/src/importers/xml.d.ts +34 -0
  66. package/index.d.ts +3 -0
  67. package/package.json +78 -4
  68. package/pool/index.d.ts +23 -0
  69. package/pool/index.js +85 -0
  70. package/pool/worker.js +27 -0
  71. package/report.d.ts +638 -0
  72. package/schema.json +2517 -0
@@ -0,0 +1,83 @@
1
+ /** A worksheet's XML up to and including <sheetData> (what must be known before the rows: widths, frozen rows, outline). */
2
+ export function sheetHead({ widths, frozenRows, outlineLevelRow }: {
3
+ widths: any;
4
+ frozenRows: any;
5
+ outlineLevelRow: any;
6
+ }): string;
7
+ /** The rest of a worksheet after its rows: merges (they come after <sheetData>, so they can be gathered while rows stream). */
8
+ export function sheetTail(merges: any, drawing?: boolean): string;
9
+ export function colName(c: any): string;
10
+ /**
11
+ * @typedef {{ numFmt?: string|null, bold?: boolean, fill?: boolean }} CellStyle
12
+ * @typedef {{ v?: any, f?: string, style?: CellStyle }} Cell
13
+ */
14
+ export class Sheet {
15
+ /** @param {Book} book @param {string} name */
16
+ constructor(book: Book, name: string);
17
+ book: Book;
18
+ name: string;
19
+ /** @type {string[]} */ xml: string[];
20
+ /** @type {number[]} */ widths: number[];
21
+ /** @type {string[]} */ merges: string[];
22
+ /** @type {{ bytes: Uint8Array, ext: string, col: number, row: number, w: number, h: number }[]} */ images: {
23
+ bytes: Uint8Array;
24
+ ext: string;
25
+ col: number;
26
+ row: number;
27
+ w: number;
28
+ h: number;
29
+ }[];
30
+ rowCount: number;
31
+ frozenRows: number;
32
+ outlineLevelRow: number;
33
+ /**
34
+ * Add a row (1-based number returned). cells: one per column (null = empty).
35
+ * @param {(Cell|null)[]} cells @param {{ outlineLevel?: number }} [opt]
36
+ */
37
+ addRow(cells: (Cell | null)[], opt?: {
38
+ outlineLevel?: number;
39
+ }): number;
40
+ /** Merge (1-based, inclusive). */
41
+ merge(r1: any, c1: any, r2: any, c2: any): void;
42
+ /** A picture with its top-left at (col, row), 0-based, w × h pixels. */
43
+ addImage(bytes: any, ext: any, col: any, row: any, w: any, h: any): void;
44
+ /** @param {number} i */
45
+ toXml(i: number): string;
46
+ }
47
+ export class Book {
48
+ constructor({ title, creator }?: {
49
+ title?: string;
50
+ creator?: string;
51
+ });
52
+ title: string;
53
+ creator: string;
54
+ fullCalcOnLoad: boolean;
55
+ /** @type {Sheet[]} */ sheets: Sheet[];
56
+ /** @type {Map<string, number>} */ styles: Map<string, number>;
57
+ /** @type {Map<string, number>} */ numFmts: Map<string, number>;
58
+ /** @param {string} name */
59
+ addSheet(name: string): Sheet;
60
+ /** cellXfs index for a style */
61
+ style(st: CellStyle | undefined): number;
62
+ /** One <c> element. @param {string} ref @param {Cell} c */
63
+ cell(ref: string, c: Cell): string;
64
+ stylesXml(): string;
65
+ /** The .xlsx bytes. */
66
+ write(): Uint8Array<ArrayBuffer>;
67
+ /**
68
+ * The workbook's own parts, written after the sheets (their names, the styles they used): styles, workbook,
69
+ * relationships, properties and content types. over: [part, content type] of the sheets and drawings.
70
+ * @param {[string, string][]} over @returns {[string, string][]}
71
+ */
72
+ bookParts(over: [string, string][]): [string, string][];
73
+ }
74
+ export type CellStyle = {
75
+ numFmt?: string | null;
76
+ bold?: boolean;
77
+ fill?: boolean;
78
+ };
79
+ export type Cell = {
80
+ v?: any;
81
+ f?: string;
82
+ style?: CellStyle;
83
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Parse an XML document into its root element.
3
+ * @param {string} src
4
+ * @param {Partial<typeof XML_LIMITS>} [limits]
5
+ * @returns {XmlElement}
6
+ */
7
+ export function parseXml(src: string, limits?: Partial<typeof XML_LIMITS>): XmlElement;
8
+ /**
9
+ * A small XML reader for untrusted files (SSRS .rdl). Pure JavaScript, no DOM, so it runs anywhere.
10
+ *
11
+ * Safe by construction:
12
+ * - no DTDs: <!DOCTYPE and <!ENTITY are refused, so there is no entity expansion (XXE, billion laughs);
13
+ * only the five XML entities and character references are read;
14
+ * - caps on size, nesting depth, element count and attributes per element;
15
+ * - linear time: it scans with indexOf and character tests, no regular expression runs over the input.
16
+ *
17
+ * Elements come back as { name, ns, attrs, children, text }: name is the local name (prefix removed),
18
+ * ns the namespace URI, text the element's own character data (CDATA included).
19
+ */
20
+ export class XmlError extends Error {
21
+ }
22
+ export namespace XML_LIMITS {
23
+ let maxChars: number;
24
+ let maxDepth: number;
25
+ let maxElements: number;
26
+ let maxAttributes: number;
27
+ }
28
+ export type XmlElement = {
29
+ name: string;
30
+ ns: string;
31
+ attrs: Record<string, string>;
32
+ children: XmlElement[];
33
+ text: string;
34
+ };
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ // @reportwright/engine types: the API (generated from the engine's JSDoc into dist/types) and the report schema.
2
+ export * from './dist/types/packages/engine/entry.js';
3
+ export type * from './report.js';
package/package.json CHANGED
@@ -1,6 +1,80 @@
1
1
  {
2
2
  "name": "@reportwright/engine",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.11.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "license": "MIT",
9
+ "author": "Arun Mallikarjun",
10
+ "homepage": "https://github.com/MrArun005/reportwright/tree/main/packages/engine#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/MrArun005/reportwright/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/MrArun005/reportwright.git",
17
+ "directory": "packages/engine"
18
+ },
19
+ "keywords": [
20
+ "reportwright",
21
+ "report",
22
+ "reporting",
23
+ "pdf",
24
+ "pdf-a",
25
+ "excel",
26
+ "xlsx",
27
+ "docx",
28
+ "pptx",
29
+ "report-engine",
30
+ "jasperreports",
31
+ "ssrs",
32
+ "rdl"
33
+ ],
34
+ "description": "ReportWright report engine: JSON report definitions to pages, PDF (incl. PDF/A), Word, Excel, PowerPoint, HTML and CSV. Node and browsers. Fonts: @reportwright/fonts; CLI: @reportwright/cli.",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./index.d.ts",
38
+ "default": "./dist/index.js"
39
+ },
40
+ "./report": {
41
+ "types": "./report.d.ts"
42
+ },
43
+ "./schema.json": "./schema.json",
44
+ "./pool": {
45
+ "types": "./pool/index.d.ts",
46
+ "default": "./pool/index.js"
47
+ },
48
+ "./package.json": "./package.json"
49
+ },
50
+ "types": "./index.d.ts",
51
+ "files": [
52
+ "dist",
53
+ "pool",
54
+ "index.d.ts",
55
+ "report.d.ts",
56
+ "schema.json",
57
+ "README.md",
58
+ "CHANGELOG.md",
59
+ "LICENSE"
60
+ ],
61
+ "engines": {
62
+ "node": ">=20.9"
63
+ },
64
+ "sideEffects": false,
65
+ "dependencies": {
66
+ "pdf-lib": "1.17.1",
67
+ "@pdf-lib/fontkit": "1.1.1",
68
+ "bwip-js": "4.11.4",
69
+ "fflate": "0.8.3",
70
+ "bidi-js": "1.0.3"
71
+ },
72
+ "peerDependencies": {
73
+ "@reportwright/fonts": "0.11.0"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "@reportwright/fonts": {
77
+ "optional": true
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,23 @@
1
+ // @reportwright/engine/pool (Node only): render many reports in parallel on worker threads.
2
+ import type { Report } from '../report.js';
3
+ export interface PoolRenderOptions {
4
+ /** default pdf */
5
+ format?: 'pdf' | 'xlsx' | 'docx' | 'pptx' | 'csv' | 'html';
6
+ parameters?: Record<string, unknown>;
7
+ timeZone?: string;
8
+ timeoutMs?: number;
9
+ /** passed to the exporter: exportPdf's title, pdfa, … */
10
+ options?: Record<string, unknown>;
11
+ }
12
+ export interface Pool {
13
+ /** the file's bytes */
14
+ render(definition: Report | object, opts?: PoolRenderOptions): Promise<Uint8Array>;
15
+ /** stops the workers; renders still waiting are rejected */
16
+ close(): Promise<void>;
17
+ readonly size: number;
18
+ /** renders waiting or running */
19
+ readonly pending: number;
20
+ }
21
+ /** workers: default the CPU count less one. fontsDir: a folder of the fonts (default @reportwright/fonts).
22
+ * heapMB: each worker's heap limit (default 1024). timeoutMs: stop a render past it with status 504 (default 0: no limit). */
23
+ export function createPool(opt?: { workers?: number; fontsDir?: string; maxQueue?: number; heapMB?: number; timeoutMs?: number }): Pool;
package/pool/index.js ADDED
@@ -0,0 +1,85 @@
1
+ // @reportwright/engine/pool (Node only): render many reports in parallel on worker threads.
2
+ // const pool = createPool({ workers: 4 });
3
+ // const pdf = await pool.render(definition, { format: 'pdf', parameters: { id: 42 } });
4
+ // await pool.close();
5
+ import { Worker } from 'node:worker_threads';
6
+ import os from 'node:os';
7
+
8
+ /**
9
+ * @typedef {{ format?: 'pdf' | 'xlsx' | 'docx' | 'pptx' | 'csv' | 'html', parameters?: Record<string, unknown>,
10
+ * timeZone?: string, timeoutMs?: number, options?: Record<string, unknown> }} PoolRenderOptions
11
+ * options: passed to the exporter (exportPdf's title, pdfa, …)
12
+ * @typedef {{ render: (definition: object, opts?: PoolRenderOptions) => Promise<Uint8Array>, close: () => Promise<void>, readonly size: number, readonly pending: number }} Pool
13
+ */
14
+
15
+ /**
16
+ * A pool of worker threads, each with the engine and its fonts loaded once.
17
+ * @param {{ workers?: number, fontsDir?: string, maxQueue?: number, heapMB?: number, timeoutMs?: number }} [opt]
18
+ * workers: default the CPU count less one. fontsDir: a folder of the fonts (default @reportwright/fonts).
19
+ * maxQueue: renders waiting beyond it are refused (default 10,000).
20
+ * heapMB: each worker's heap limit (default 1024): a render past it fails alone (its worker is replaced), instead of
21
+ * taking the whole process down. timeoutMs: a render running longer is stopped (status 504) and its worker replaced
22
+ * (default: no limit; the engine's own opts.timeoutMs per render is checked between rows, this one always holds).
23
+ * @returns {Pool}
24
+ */
25
+ export function createPool({ workers = Math.max(1, (os.availableParallelism?.() ?? os.cpus().length) - 1), fontsDir, maxQueue = 10000, heapMB = 1024, timeoutMs = 0 } = {}) {
26
+ if (!Number.isInteger(workers) || workers < 1) throw new TypeError('createPool({ workers }): workers must be a whole number of at least 1');
27
+ if (!Number.isFinite(heapMB) || heapMB < 32) throw new TypeError('createPool({ heapMB }): heapMB must be a number of at least 32');
28
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw new TypeError('createPool({ timeoutMs }): timeoutMs must be 0 (no limit) or more');
29
+ /** @type {{ def: object, opts: PoolRenderOptions, resolve: (b: Uint8Array) => void, reject: (e: Error) => void }[]} */
30
+ const queue = [];
31
+ /** @type {Set<Worker>} */
32
+ const idle = new Set();
33
+ /** @type {Map<Worker, (typeof queue)[number]>} */
34
+ const busy = new Map();
35
+ /** @type {Map<Worker, any>} */
36
+ const timers = new Map();
37
+ let closed = false;
38
+ const done = (w) => { clearTimeout(timers.get(w)); timers.delete(w); const job = busy.get(w); busy.delete(w); return job; };
39
+ const spawn = () => {
40
+ const w = new Worker(new URL('./worker.js', import.meta.url), { workerData: { fontsDir }, resourceLimits: { maxOldGenerationSizeMb: heapMB } });
41
+ w.on('message', (msg) => {
42
+ const job = done(w);
43
+ if (job) msg.error ? job.reject(Object.assign(new Error(msg.error.message), msg.error)) : job.resolve(new Uint8Array(msg.bytes));
44
+ idle.add(w);
45
+ next();
46
+ });
47
+ w.on('error', (e) => {
48
+ const oom = /** @type {any} */ (e)?.code === 'ERR_WORKER_OUT_OF_MEMORY';
49
+ done(w)?.reject(oom ? Object.assign(new Error(`the render ran out of memory (heapMB: ${heapMB}) and was stopped`), { status: 500, code: e.code }) : e);
50
+ });
51
+ w.on('exit', () => {
52
+ idle.delete(w);
53
+ done(w)?.reject(new Error('the render worker stopped'));
54
+ if (!closed) { idle.add(spawn()); next(); } // a crashed worker is replaced
55
+ });
56
+ return w;
57
+ };
58
+ const next = () => {
59
+ while (queue.length && idle.size) {
60
+ const w = /** @type {Worker} */ (idle.values().next().value);
61
+ idle.delete(w);
62
+ const job = /** @type {(typeof queue)[number]} */ (queue.shift());
63
+ busy.set(w, job);
64
+ // past the time limit the worker is stopped (a render in a long synchronous step cannot be interrupted otherwise)
65
+ if (timeoutMs > 0) timers.set(w, setTimeout(() => { done(w)?.reject(Object.assign(new Error(`the render ran longer than ${timeoutMs} ms and was stopped`), { status: 504 })); w.terminate(); }, timeoutMs));
66
+ w.postMessage({ def: job.def, opts: job.opts });
67
+ }
68
+ };
69
+ for (let i = 0; i < workers; i++) idle.add(spawn());
70
+ return {
71
+ get size() { return workers; },
72
+ get pending() { return queue.length + busy.size; },
73
+ render(def, opts = {}) {
74
+ if (closed) return Promise.reject(new Error('the pool is closed'));
75
+ if (!def || typeof def !== 'object') return Promise.reject(new TypeError('pool.render(definition, opts): definition must be a report object'));
76
+ if (queue.length >= maxQueue) return Promise.reject(new Error(`the pool queue is full (${maxQueue} renders waiting)`));
77
+ return new Promise((resolve, reject) => { queue.push({ def, opts, resolve, reject }); next(); });
78
+ },
79
+ async close() {
80
+ closed = true;
81
+ for (const job of queue.splice(0)) job.reject(new Error('the pool is closed'));
82
+ await Promise.all([...idle, ...busy.keys()].map(w => w.terminate()));
83
+ },
84
+ };
85
+ }
package/pool/worker.js ADDED
@@ -0,0 +1,27 @@
1
+ // One render thread of @reportwright/engine/pool: the engine and the fonts load once, then one job at a time.
2
+ import { parentPort, workerData } from 'node:worker_threads';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { render, FontStore, fontFile, defaultFontStore, exportPdf, exportXlsx, exportDocx, exportPptx, exportHtml, exportCsv, needsExportData } from '@reportwright/engine';
6
+
7
+ const fonts = workerData?.fontsDir ? new FontStore((key) => fs.readFile(path.join(workerData.fontsDir, fontFile(key)))) : defaultFontStore();
8
+ const EXPORT = {
9
+ pdf: (m, o) => exportPdf(m, { fonts, ...o }),
10
+ xlsx: (m, o) => exportXlsx(m, { fonts, ...o }),
11
+ docx: (m, o) => exportDocx(m, { fonts, ...o }),
12
+ pptx: (m, o) => exportPptx(m, { fonts, ...o }),
13
+ html: async (m, o) => new TextEncoder().encode(await exportHtml(m, fonts, o)),
14
+ csv: async (m, o) => new TextEncoder().encode(exportCsv(m, o)),
15
+ };
16
+ /** @type {import('node:worker_threads').MessagePort} */ (parentPort).on('message', async ({ def, opts }) => {
17
+ try {
18
+ const format = opts.format || 'pdf';
19
+ if (!EXPORT[format]) throw new Error(`unknown format "${format}" (pdf, xlsx, docx, pptx, html, csv)`);
20
+ const model = await render(def, { fontStore: fonts, parameters: opts.parameters, timeZone: opts.timeZone, timeoutMs: opts.timeoutMs, exportData: needsExportData(format, opts.options) });
21
+ const bytes = await EXPORT[format](model, opts.options || {});
22
+ const buf = bytes.buffer.byteLength === bytes.byteLength ? bytes.buffer : bytes.slice().buffer;
23
+ parentPort?.postMessage({ bytes: buf }, [buf]);
24
+ } catch (e) {
25
+ parentPort?.postMessage({ error: { message: String(e?.message || e), status: e?.status } });
26
+ }
27
+ });