@reportwright/engine 0.0.0-stage → 0.12.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 (79) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/LICENSE +23 -0
  3. package/README.md +105 -2
  4. package/dist/index.js +22548 -0
  5. package/dist/pdfstreamworker.js +38 -0
  6. package/dist/types/packages/engine/entry.d.ts +125 -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/stream.d.ts +48 -0
  12. package/dist/types/src/engine/data/xml.d.ts +108 -0
  13. package/dist/types/src/engine/expr/evaluate.d.ts +183 -0
  14. package/dist/types/src/engine/expr/format.d.ts +122 -0
  15. package/dist/types/src/engine/expr/javafmt.d.ts +26 -0
  16. package/dist/types/src/engine/expr/parser.d.ts +29 -0
  17. package/dist/types/src/engine/image.d.ts +107 -0
  18. package/dist/types/src/engine/index.d.ts +239 -0
  19. package/dist/types/src/engine/items/barcode.d.ts +121 -0
  20. package/dist/types/src/engine/items/cells.d.ts +11 -0
  21. package/dist/types/src/engine/items/chart-kit.d.ts +75 -0
  22. package/dist/types/src/engine/items/chart-more.d.ts +52 -0
  23. package/dist/types/src/engine/items/chart-round.d.ts +28 -0
  24. package/dist/types/src/engine/items/chart.d.ts +42 -0
  25. package/dist/types/src/engine/items/hpage.d.ts +33 -0
  26. package/dist/types/src/engine/items/index.d.ts +117 -0
  27. package/dist/types/src/engine/items/map.d.ts +11 -0
  28. package/dist/types/src/engine/items/paint.d.ts +138 -0
  29. package/dist/types/src/engine/items/pivot.d.ts +45 -0
  30. package/dist/types/src/engine/items/rich.d.ts +105 -0
  31. package/dist/types/src/engine/items/toc.d.ts +10 -0
  32. package/dist/types/src/engine/items/visuals.d.ts +5 -0
  33. package/dist/types/src/engine/layout.d.ts +24 -0
  34. package/dist/types/src/engine/paged.d.ts +19 -0
  35. package/dist/types/src/engine/paginate/index.d.ts +17 -0
  36. package/dist/types/src/engine/reuse.d.ts +120 -0
  37. package/dist/types/src/engine/schema/report.schema.d.ts +2421 -0
  38. package/dist/types/src/engine/schema/template.d.ts +212 -0
  39. package/dist/types/src/engine/schema/validate.d.ts +21 -0
  40. package/dist/types/src/engine/stream.d.ts +54 -0
  41. package/dist/types/src/engine/style.d.ts +70 -0
  42. package/dist/types/src/engine/text/fonts.d.ts +96 -0
  43. package/dist/types/src/engine/text/measure.d.ts +145 -0
  44. package/dist/types/src/engine/text/rich.d.ts +48 -0
  45. package/dist/types/src/engine/text/shaper.d.ts +20 -0
  46. package/dist/types/src/engine/units.d.ts +53 -0
  47. package/dist/types/src/engine/url.d.ts +23 -0
  48. package/dist/types/src/exporters/color.d.ts +9 -0
  49. package/dist/types/src/exporters/csv.d.ts +44 -0
  50. package/dist/types/src/exporters/deadline.d.ts +6 -0
  51. package/dist/types/src/exporters/docx.d.ts +19 -0
  52. package/dist/types/src/exporters/encrypt.d.ts +39 -0
  53. package/dist/types/src/exporters/figures.d.ts +112 -0
  54. package/dist/types/src/exporters/html.d.ts +24 -0
  55. package/dist/types/src/exporters/htmldata.d.ts +32 -0
  56. package/dist/types/src/exporters/pdf.d.ts +45 -0
  57. package/dist/types/src/exporters/pdfa.d.ts +42 -0
  58. package/dist/types/src/exporters/pdfpaint.d.ts +107 -0
  59. package/dist/types/src/exporters/pdfstream.d.ts +198 -0
  60. package/dist/types/src/exporters/pdfstreamtags.d.ts +38 -0
  61. package/dist/types/src/exporters/pdfua.d.ts +17 -0
  62. package/dist/types/src/exporters/png.d.ts +10 -0
  63. package/dist/types/src/exporters/pptx.d.ts +14 -0
  64. package/dist/types/src/exporters/regions.d.ts +30 -0
  65. package/dist/types/src/exporters/subset.d.ts +5 -0
  66. package/dist/types/src/exporters/svg.d.ts +26 -0
  67. package/dist/types/src/exporters/svgimage.d.ts +33 -0
  68. package/dist/types/src/exporters/xlsx.d.ts +61 -0
  69. package/dist/types/src/exporters/xlsxchart.d.ts +26 -0
  70. package/dist/types/src/exporters/xlsxwriter.d.ts +83 -0
  71. package/dist/types/src/importers/xml.d.ts +34 -0
  72. package/examples/stream-1m.mjs +87 -0
  73. package/index.d.ts +3 -0
  74. package/package.json +79 -4
  75. package/pool/index.d.ts +23 -0
  76. package/pool/index.js +85 -0
  77. package/pool/worker.js +27 -0
  78. package/report.d.ts +638 -0
  79. package/schema.json +2517 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,233 @@
1
+ # Changelog
2
+
3
+ All `@reportwright/*` packages (formerly `@pagewrightjs/*`) share one version. The versioning policy is in [CONTRIBUTING.md](CONTRIBUTING.md#versioning-semver).
4
+
5
+ ## 0.12.0 — 2026-10-09
6
+
7
+ Streaming PDF: very large reports are written page by page, with flat memory.
8
+
9
+ ### Added
10
+ - **`exportPdfStream(definition, options, sink)`** in `@reportwright/engine`: a report whose body is a flowing table
11
+ (plain or grouped: repeating group headers, subtotals, `RowNumber()`, widow control, keep-together, page breaks) is laid
12
+ out a window of rows at a time and each finished page goes straight to the sink. Peak memory stays flat: about 260 MB
13
+ for the whole render process from 20,000 to 100,000 rows (projected about 250–275 MB at 1,000,000).
14
+ "Page N of M" and footer totals work (a counting pass first). PDF/A-2b, PDF/UA-1 and PDF/A-2a + PDF/UA-1 stream too
15
+ (veraPDF passes). Anything else (images, charts, matrices, rich text, subreports, signing, encryption) falls back to
16
+ `exportPdf`, with the same output.
17
+ - **The server streams large PDFs** (`/api/reports/<id>/pdf`, `/api/v1/render`) with the same row caps, deadline, stall
18
+ timeout and abort handling as the CSV/Excel streaming; the viewer's large-report button gets the streamed file.
19
+ - **`parallel: N`** (Node, opt-in): page ranges painted on worker threads, byte-identical to the serial file. Measure on
20
+ your hardware before relying on it; each worker costs about 150 MB.
21
+ - `examples/stream-1m.mjs` in the engine package: streams a generated million-row ledger to a file and prints peak memory.
22
+ - `planPaged` exported for custom pipelines.
23
+
24
+ ### Changed
25
+ - The PDF painter is shared between `exportPdf` and the streaming writer; `exportPdf` output is byte-for-byte unchanged.
26
+ - The engine package ships its readable bundle without a source map (1.3 MB unpacked).
27
+
28
+ ### Known limits
29
+ - In the npm engine, grouped reports sort rows in memory (the server spills them to disk), so a 1M-row grouped report is
30
+ not flat outside the server yet.
31
+
32
+ ## 0.11.0 — 2026-10-08
33
+
34
+ Everything since 0.10.0. Items marked **Behaviour change** alter output that a pixel-locked layout or a locale-sensitive
35
+ export may depend on: check them before you upgrade.
36
+
37
+ ### Behaviour changes
38
+ - **Numbers and dates shrink instead of wrapping.** An amount inside a text box shrinks like a number, a short code or a
39
+ header word shrinks before it breaks inside (`Amou|nt`, `m0|2`), and a short text shrinks before it is cut with `…`
40
+ or wraps alone in a one-line box. Reports that relied on the old wrap need a wider box or `shrinkToFit: false`.
41
+ - **Widow control is on by default** for every group: a heading or a lone row no longer ends a page. `widowControl: false`
42
+ turns it off. Page counts can change for reports that break inside groups.
43
+ - **A group taller than a page splits** with at least two rows on each side of the break, so no lone row sits at a page
44
+ foot or head. A keep-together block no longer leaves a blank page.
45
+ - **Sideways tables spread their column sets evenly** over the pages they need (a balanced split, not a long last page).
46
+ - **Number grouping follows Intl by default** (es-ES shows `1234,50`). The report option `grouping` is `always` or
47
+ `never`. Unformatted numbers use a Latin decimal sign with Latin digits (ar-SA).
48
+ - **Page size names are forgiving.** `"a4 "` and `"letter"` resolve to A4 and Letter. An unknown size (`"A44"`) still
49
+ falls back to A4 with a warning and a suggestion.
50
+ - **Data bars:** a value label sits after the bar when it fits, else inside it, right-aligned, in a contrasting ink.
51
+ Negative values draw from a zero line in `negativeColor` (default red); positive-only columns are unchanged.
52
+ - **Charts:** column category labels turn (-45) when they do not fit flat, instead of dropping every other label. Scatter
53
+ and bubble axes pad the automatic range so the largest point is not on the frame. `outliers: clip | none` decides what
54
+ happens to points past an explicit axis min or max; clipped points get a ring and a warning. A scatter or bubble row
55
+ with no x or y is no longer drawn at 0 (one warning counts them).
56
+ - **Fetched CSV, XML and JSON** read the response charset (ISO-8859-1, windows-1252, ...); UTF-8 is the default and a BOM
57
+ is stripped.
58
+ - **Fetch is SSRF-guarded by default:** http(s) only, private and local addresses refused in every spelling, DNS checked
59
+ in Node, each redirect re-checked, parameters fill the path and query only. `allowHosts` and `unsafeFetch` opt out.
60
+ - **Expressions read own members only:** `constructor`, `__proto__` and other inherited names give `#Error`.
61
+ - **Data fetch errors** name the cause (DNS, refused, reset, TLS, timeout) and redact the URL (query shown as `?…`, no
62
+ userinfo, no keys or tokens).
63
+ - **Charts and barcodes get generated alt text** (type, title, series, categories; symbology and value) when the author
64
+ sets none. The author's alt text always wins.
65
+ - **Subreport report ids** may use capitals and underscores (still no `/`, `\`, `.` or `..`).
66
+ - **Deterministic output:** structure tag ids come from the render's counter, so two renders give one model.
67
+ `exportPdf` takes `creationDate`, or `SOURCE_DATE_EPOCH`; then the dates are UTC and the document ID derives from the
68
+ content.
69
+ - **PDF images that cannot be embedded** (GIF, WebP, undecodable, a URL with no `fetchImage`, too large) are left out
70
+ with a warning that names the picture and the reason. A cut or damaged PNG is checked and left out instead of hanging.
71
+
72
+ ### Added
73
+ - **Vercel hosting.** `GET /api/v1/cron` runs the schedule tick and the batch queue when `CRON_SECRET` (16+ characters) is set, with `Authorization: Bearer $CRON_SECRET`; without it the route answers 404. `vercel.json` runs it daily. On Vercel, a queued batch runs after its response.
74
+ - `PW_STORAGE` falls back to `DATABASE_URL` when unset, on Vercel or with `PW_STORAGE_FROM_DATABASE_URL=1` only (documented in docs/CONFIG.md).
75
+ - **Designer: Import report** (File group) reads `.jrxml`, `.rdl`, `.rdlc`, `.rptdesign` and `.json` definitions through
76
+ the importers, and shows the importer's warnings. The CLI (`pw import`) does the same.
77
+ - **ViewerHandle:** `setParameters`, `setDefinition`, `goToPage`, `getPageCount`, `print`, `export` (Blob), and `on(ready |
78
+ error | page | parameters)`. The React ref gets the handle. Documented in the viewer README.
79
+ - **validate()** warns about unknown format codes (`N2x`), colour names (`redd`), named styles, sort entries not shaped
80
+ `{by, dir}`, date defaults that are not dates, charts with no series, local image paths, and unknown or padded page
81
+ sizes. Each warning has a did-you-mean where one exists.
82
+ - **Word export:** heading items are Heading 1 to 6 paragraphs, and the document language is the report locale by default
83
+ (`lang` still wins).
84
+ - **Tagged PDF:** link annotations say the link's visible text, else its tooltip, else its URL.
85
+ - **PDF drill-through links** open the viewer with their parameters (a URI when the base URL is known). URIs are ASCII,
86
+ the outline holds headings and nests group bookmarks, and every PDF has page labels.
87
+ - **Types:** `render()` takes a `RenderOptions` type. Sort entries are `{by, dir}` (`SortSpec`) on data sets, tables and
88
+ lists. `report.d.ts` is regenerated.
89
+ - **Strict CSP:** the viewer and the Designer run under `style-src 'self'` with no style element and no style attribute
90
+ written by the engine. See the viewer README.
91
+ - **Fonts:** `defaultFontStore({ fontsUrl })` reads a copied fonts folder in bundled apps. A font that does not load is named
92
+ on screen and `onReady` reports it. Faces load lazily, only those a report uses; a face that fails to load falls back to a
93
+ bundled face with a warning.
94
+
95
+ ### Changed
96
+ - **Designer accessibility:** Inspector section headings are real buttons (Enter and Space toggle them). Chart gallery
97
+ choices are named by the chart type alone. The Designer's embedded font rules go through a constructable stylesheet.
98
+ - **Viewer export menu:** each format is named by its format ("PDF document"), with the badge hidden from screen readers.
99
+ - **Toolbar:** `toolbar.hide` takes group keys (`export`, `search`, `zoom`, `navigation`), `sidebar`, and every format id.
100
+ No Export gear when no format is left.
101
+ - **Optional CJK and emoji faces** do not add a font warning; their own `NO_CJK_FONTS` warning covers them.
102
+
103
+ ### Fixed
104
+ - Subreport ids and render warnings: a render's warning list holds each message once, and a barcode warning names an
105
+ unnamed item by place instead of `undefined`.
106
+ - Text with objects, arrays or large numbers from data prints as data, not `[object Object]` or `1e+21`.
107
+ - Decimal sums stay exact above 2^49.
108
+ - Designer seq fields format as codes (no decimals, no grouping).
109
+ - Tables take column widths from the content (`fitColumns`, and columns with no width).
110
+ - Styles that are not objects (`"bold"`) are ignored with a validate() warning instead of crashing the render.
111
+ - Expression and render guards: a traversal subreport id or a parameter value like `../x` is refused before `loadReport`.
112
+ - Server renders: image URLs never reach internal hosts, dev mode included.
113
+ - Vite bundles no longer warn 'cannot be analyzed' for the Node-only sharp import.
114
+
115
+ ## 0.10.0
116
+
117
+ Streaming exports and a pool that uses every core. No change to rendering, totals or page counts. Design and measurements:
118
+ [docs/research/STREAMING-1M.md](docs/research/STREAMING-1M.md).
119
+
120
+ ### Added
121
+ - **Streaming data CSV and Excel.** A report with one table and row-by-row expressions (group and table footers may
122
+ total with Sum, Count, Avg, Min, Max or CountRows) exports as it reads: rows come from the source a batch at a time
123
+ (JSON arrays split from the byte stream, CSV, SQLite, PostgreSQL and MySQL cursors), are sorted and grouped through
124
+ temporary files when needed, and are written to the response with back-pressure. Memory stays flat: 100k → 300k rows
125
+ measured 160 → 168 MB for a CSV render, 185 → 197 MB for a grouped Excel one (the whole render process); 1M rows
126
+ projects under 300 MB. Same bytes as before for CSV, same cells and formulas for Excel. Other reports take the page
127
+ model as before. The answer has no `content-length`; a failure after the first bytes breaks the download off (CSV:
128
+ a last line `#ERROR <reference>`). Settings: `PW_STREAM`, `PW_STREAM_MAX_ROWS`, `PW_STREAM_FETCH_MAX_MB`,
129
+ `PW_STREAM_SQL_MAX_MB`, `PW_SPILL_MAX_MB`, `PW_SPILL_TOTAL_MB`, `PW_STREAM_STALL_MS`.
130
+ - Excel tables longer than 1,048,576 rows continue on a sheet "Name (2)" with their header rows again.
131
+ - `@reportwright/engine/pool`: `createPool({ heapMB, timeoutMs })`.
132
+
133
+ ### Changed
134
+ - The server's render pool has a worker per core (less one, at most 16); memory decides what runs at once, by an
135
+ estimate per template and caller, with small heaps for small and streaming renders (`PW_RENDER_SMALL_HEAP_MB`) and
136
+ a cap on one caller's running renders (`PW_RENDER_RUNNING_PER_PRINCIPAL`). `UV_THREADPOOL_SIZE` follows the cores
137
+ (Dockerfile, cluster.mjs).
138
+ - Excel: a plain cell no longer carries a duplicate style; the no-table CSV starts with a BOM like the others.
139
+
140
+ ### Fixed
141
+ - SQLite sources: a reused query process failed every second query ("statement has been finalized").
142
+
143
+ ## 0.9.0
144
+
145
+ Pagewright is now **ReportWright** (formerly Pagewright). No change to rendering, totals or page counts.
146
+
147
+ ### Changed
148
+ - The npm packages moved to the `@reportwright` scope: `@reportwright/engine`, `/viewer`, `/fonts`, `/fonts-cjk`,
149
+ `/cli`, `/react`, `/vue`, `/svelte`, `/angular`. The `@pagewrightjs/*` packages are deprecated and get no updates.
150
+ Change the package names in your `package.json` and imports; nothing else.
151
+ - The brand in the app, the Designer, the viewer, mails, the 9 interface languages, the CLI, error messages and
152
+ exports (the PDF Producer and Creator, HTML titles) reads ReportWright.
153
+ - The CLI installs as `reportwright` and still as `pw`; the CJK font downloader as `reportwright-fonts-cjk` (and
154
+ `pagewright-fonts-cjk`).
155
+ - The repository is `github.com/MrArun005/reportwright` (the old URL redirects).
156
+
157
+ ### Deprecated (removed in 1.0)
158
+ - `<pagewright-viewer>` and `<pagewright-designer>` (now `<reportwright-viewer>`, `<reportwright-designer>`; both are
159
+ registered), `definePagewrightElements` (now `defineReportWrightElements`), `PagewrightViewer` and
160
+ `PagewrightDesigner` in React and Vue (now `ReportWrightViewer`, `ReportWrightDesigner`), `pagewrightViewer` and
161
+ `pagewrightDesigner` in Svelte (now `reportWrightViewer`, `reportWrightDesigner`), `window.Pagewright` (now
162
+ `window.ReportWright`) and `/embed/pagewright-viewer.js` (now `/embed/reportwright-viewer.js`). Each old name is
163
+ an alias of the new one.
164
+
165
+ ### Kept (so deployments and stored data keep working)
166
+ - Settings keep the `PW_` prefix. The `x-pagewright-*` headers, the `pagewright_*` metrics, cookie and storage table
167
+ names, the `.pw.json` extension and `"$schema": "pagewright/report@1"` are unchanged; `reportwright/report@1` is
168
+ accepted as the same format.
169
+
170
+ ## 0.8.1
171
+
172
+ Supply-chain hardening (what Socket.dev and similar scanners read). No change to rendering or exports.
173
+
174
+ ### Changed
175
+ - `@pagewrightjs/viewer` no longer bundles pdf-lib, @pdf-lib/fontkit, bwip-js, fflate or bidi-js: they are
176
+ dependencies pinned to exact versions, as in the engine. Import the viewer through a bundler (Vite, webpack,
177
+ esbuild, Rollup…). Unpacked size 14.8 MB to 10.1 MB.
178
+ - The engine and the CLI import bidi-js too (a pinned dependency) instead of bundling its Unicode tables.
179
+ - `@pagewrightjs/cli` holds the report commands only (render, test, diff, import): no server modules, no
180
+ `child_process`, no environment reads. `pw audit` and `pw migrate-storage` run from the app (`node bin/pw.mjs`).
181
+ - No line over 1,000 characters in any package (source maps aside).
182
+ - `render(def, { fetch })` is the documented way to give the engine its network access; `globalThis.fetch` is only
183
+ the fallback, and the error without either says so.
184
+ - `@pagewrightjs/fonts` ships a LICENSE file.
185
+
186
+ ### Added
187
+ - `npm run scan:packages` (`scripts/scan-packages.mjs`): a Socket-style scan of every packed package, run in CI and
188
+ before publishing. Release steps: [docs/RELEASING.md](docs/RELEASING.md).
189
+
190
+ ## 0.8.0
191
+
192
+ ### Breaking (with a deprecation period)
193
+ - One export signature: `exportPdf(model, { fonts, ... })`, `exportXlsx(model, { fonts, ... })`,
194
+ `exportDocx(model, { fonts, ... })`, `exportPptx(model, { fonts, ... })`. The 0.7 positional forms
195
+ (`exportPdf(model, fontStore, opt)`, `exportXlsx(model, ExcelJS, opt)`) still work in 0.8 and print one deprecation
196
+ warning; they are removed in 0.9. A wrong argument gives a `TypeError` that says what was expected.
197
+ - A table cell under another cell's `colSpan` is no longer drawn on top of it (validate() warns about it).
198
+
199
+ ### Added
200
+ - The viewer works without a server: `mountViewer(el, { definition, data, fontsUrl })` (also in the React, Vue,
201
+ Svelte and Angular wrappers). The README says what works standalone and what needs the self-hosted server.
202
+ - `colSpan` in RDL/HTML style: a row with fewer cells than columns omits the spanned cells.
203
+ - The report JSON Schema, published as `@pagewrightjs/engine/schema.json`; `report.d.ts` is generated from it and
204
+ `validate()` checks properties and choices against it (the hand-written types and the validator had drifted apart: `dir`, `alt` and about 30 engine properties were missing from one or the other).
205
+ - `pdfa: true` finds the sRGB colour profile in `@pagewrightjs/fonts` by itself.
206
+ - `@pagewrightjs/fonts-cjk`: Chinese, Japanese, Korean and emoji fonts, downloaded on demand
207
+ (`npx pagewright-fonts-cjk`), pinned and checked by SHA-256.
208
+ - `createPool({ workers })` in `@pagewrightjs/engine/pool`: renders on worker threads (4 workers: 2.8× one).
209
+ - `detectDates: false` on a data set; `colorByCategory: true` on charts (one colour per category across charts).
210
+ - `pw import report.jrxml` (also .rdl and .rptdesign) in `@pagewrightjs/cli`.
211
+ - Docs: docs/QUICKSTART.md, docs/SCHEMA.md and docs/FUNCTIONS.md (generated), docs/FROM-JASPER.md; `examples/`.
212
+
213
+ ### Changed
214
+ - The Excel export has its own writer: no ExcelJS (and none of its deprecated dependencies, `uuid` among them);
215
+ 50,000 rows: export memory +124 MB (was +409 MB), 3× faster.
216
+ - Runtime dependencies are pinned to exact versions; the unused `harfbuzzjs` dependency is gone. `npm audit`: 0.
217
+ - Every package ships readable (not minified) ES modules with source maps.
218
+ - The viewer loads other interface languages and the Excel, HTML and CSV exporters on demand: first load 847 KB
219
+ gzipped (was 877 KB minified).
220
+ - React, Vue, Svelte, Angular wrappers: a failed import shows its error in the element (`onError`); options are
221
+ compared by content, so an inline `definition` object does not remount the designer.
222
+ - Package metadata (repository, homepage, bugs, keywords, engines, `sideEffects`, typed `exports`), README badges,
223
+ this changelog in every package, SECURITY.md, a semver policy, and a GitHub workflow that publishes with npm
224
+ provenance.
225
+
226
+ ## 0.7.3
227
+ - Firefox and WebKit smoke tests in CI; settings table entries for monitoring. No API change.
228
+
229
+ ## 0.7.2, 0.7.1
230
+ - Fixes to the package builds and README quick starts.
231
+
232
+ ## 0.7.0
233
+ - First public release under the `@pagewrightjs` scope: engine, fonts, cli, viewer, react, vue, svelte, angular.
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arun Mallikarjun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ The fonts in public/fonts are under the SIL Open Font License (see their licence files).
package/README.md CHANGED
@@ -1,3 +1,106 @@
1
- # Temporary Holding Version
1
+ # @reportwright/engine
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/%40reportwright%2Fengine)](https://www.npmjs.com/package/@reportwright/engine) [![license](https://img.shields.io/badge/license-MIT-blue)](https://github.com/MrArun005/reportwright/blob/main/LICENSE) [![Socket](https://socket.dev/api/badge/npm/package/@reportwright/engine)](https://socket.dev/npm/package/@reportwright/engine) [![provenance](https://img.shields.io/badge/npm-provenance-green)](https://docs.npmjs.com/generating-provenance-statements)
4
+
5
+ The ReportWright report engine for Node and browsers: a JSON report definition and data in, pages out, then PDF (also PDF/A-2b), Word, Excel, PowerPoint (exportPptx), HTML or CSV. Includes the `pw` command line.
6
+
7
+ ```bash
8
+ npm i @reportwright/engine @reportwright/fonts
9
+ ```
10
+
11
+ ```js
12
+ import { render, defaultFontStore, exportPdf } from '@reportwright/engine';
13
+ import fs from 'node:fs';
14
+ // the fonts, the text shaper (Arabic, Hebrew, Indic, Thai…) and the PDF subsetter come from @reportwright/fonts;
15
+ // for your own fonts: new FontStore((key) => bytesOf(fontFile(key)))
16
+ const fonts = defaultFontStore();
17
+ const model = await render(JSON.parse(fs.readFileSync('statement.pw.json', 'utf8')), { fontStore: fonts, parameters: { accountId: 'LN-1' } });
18
+ // fonts are subset by default (only the glyphs the PDF uses); { subset: false } embeds every font in full
19
+ fs.writeFileSync('statement.pdf', await exportPdf(model, { fonts, title: 'Statement' }));
20
+ // a bundled browser app (Vite, webpack, Next client code): copy the fonts folder to your site and point at it, since the
21
+ // package's location is not known to the bundler. `cp -r node_modules/@reportwright/fonts/public/fonts public/pw/fonts`
22
+ const bundledFonts = defaultFontStore({ fontsUrl: '/pw/fonts/' }); // a missing font is an error; a URL or, in Node, a path
23
+ // the same options object everywhere: exportXlsx(model, { fonts }), exportDocx(model, { fonts }), exportPptx(model, { fonts })
24
+ // Excel, CSV, Word, interactive HTML and the accessible PDF rebuild tables from the table data, which render() keeps
25
+ // only when asked: render(def, { exportData: true }) (needsExportData(format) says which formats need it)
26
+ ```
27
+
28
+ ## Implicit behaviour, and how to switch it
29
+
30
+ - **ISO dates.** When a data set has no `fields`, they are detected from the rows, and a string that looks like an ISO
31
+ date (`2024-01-31`, `2024-01-31T10:00:00Z`) becomes a date. Keep such strings as text with `detectDates: false` on
32
+ the data set, or declare the fields and give that one `type: 'string'`.
33
+ - **Chart colours.** By default colours go by position. `colorByCategory: true` on charts gives each category (pie,
34
+ donut and funnel slices; `seriesGroup` series) one colour in every chart of the report that sets it.
35
+ - **Network.** Only data sources of a fetched type (`rest`, `graphql`, `soap`, `odata`, and `csv` or `xml` with a URL) and SQL sources without
36
+ `sql` make requests, through `render(def, { fetch })`. Without that option the engine uses `globalThis.fetch` behind
37
+ an SSRF guard: http(s) only; no loopback, private, link-local, CGNAT, multicast or reserved address in any spelling
38
+ (`2130706433`, `0x7f.1`, `[::ffff:127.0.0.1]`), no `localhost` name; in Node (20.16+) the request goes through
39
+ node:http(s) with the checked address pinned (no DNS rebinding; a failed lookup refuses); every redirect hop checked
40
+ again (at most 5; never https → http; another origin gets none of the source's headers); a parameter fills a URL's path and query, never its scheme and host (unless the
41
+ whole URL is a parameter and its host is in `allowHosts`). `allowHosts: ['10.0.0.5']` lets named hosts through;
42
+ `unsafeFetch: true` turns the guard off. Pass your own `fetch` to add auth, a proxy or an allow-list, or to forbid
43
+ requests altogether: `render(def, { fetch: () => { throw new Error('no network') } })`.
44
+ - **Fetch limits.** Every data request (yours too) stops after `fetchTimeoutMs` (30 s, never past the render's
45
+ `timeoutMs`) and after `maxFetchBytes` (64 MB, counted decoded, so a gzip bomb stops too). Exporters take
46
+ `timeoutMs` as well; the PDF exporter checks every PNG before decoding it and leaves a damaged one out (`onWarning`).
47
+ - **Currency.** `C2` with no `currency` on the report uses the locale's own (`en-IN` ₹, `de-DE` €, `en-US` $).
48
+
49
+ ## Streaming PDF for large reports
50
+
51
+ `exportPdfStream(definition, options, sink)` writes the PDF as it is laid out: a flowing table is laid out a window of
52
+ rows at a time and each page leaves memory once it is written, so a million-row report's PDF needs about the memory of a
53
+ few pages (measured: peak RSS flat from 20,000 to 100,000 rows, about 270 MB for the whole Node process). The file reads as
54
+ `exportPdf`'s: the same pages, text and positions.
55
+
56
+ ```js
57
+ import fs from 'node:fs';
58
+ import { exportPdfStream } from '@reportwright/engine';
59
+
60
+ const out = fs.createWriteStream('ledger.pdf');
61
+ const r = await exportPdfStream(definition, { parameters: { year: 2026 }, title: 'Ledger' }, out);
62
+ out.end();
63
+ console.log(r.streamed ? `${r.pages} pages, streamed` : `written whole: ${r.why}`);
64
+ ```
65
+
66
+ The sink is a Node `Writable` or anything with `write(bytes)` that may return a promise (back-pressure); it is not
67
+ closed for you. Options are `render()`'s and `exportPdf`'s (`fonts`, `parameters`, `title`, `pdfa`, `tagged`, …), plus
68
+ `window` (rows laid out at once, 1,000), `maxRows` (rows read, 5,000,000) and `parallel` (Node: that many worker threads
69
+ lay out and paint the pages; the file is byte for byte the same; each thread loads the engine and fonts, so it costs
70
+ memory: about 150 MB a thread). Rows come from the report's source a batch at a time: CSV and REST (JSON) sources are read
71
+ as streams, and `sqlRows(source, params)`, an async iterable of row batches, stands in for a database cursor.
72
+
73
+ **What streams:** a body that is one table (with items above or below it), its rows' expressions row by row (fields,
74
+ parameters, functions of the row, `RowNumber()`), groups with headers that repeat, subtotals (`Sum`, `Count`, `Avg`,
75
+ `Min`, `Max` of a row expression, `CountRows()`), keep-together and page breaks, filters and sorts, page headers and
76
+ footers without aggregates ("Page N of M" included: the pages are counted first, so the source is read twice), PDF/A-2b,
77
+ and accessible PDFs (PDF/UA-1, PDF/A-2a; validated with veraPDF).
78
+
79
+ **What is written whole** (`exportPdf`, the result says why): matrices, charts, lists, subreports, images, rich text,
80
+ lookups and aggregates outside footers, more than one data set, group sections (`newSection`), signed or encrypted PDFs.
81
+ Grouped and sorted tables hold their rows in the sort unless `sorter` spills them to disk (the ReportWright server does).
82
+
83
+ `examples/stream-1m.mjs` streams a generated million-row ledger to a file and prints its peak memory:
84
+ `node node_modules/@reportwright/engine/examples/stream-1m.mjs 1000000 ledger.pdf` (`--grouped`, `--tagged`,
85
+ `--parallel=4`, `--plain`).
86
+
87
+ ## Many reports at once (Node): a worker pool
88
+
89
+ ```js
90
+ import { createPool } from '@reportwright/engine/pool';
91
+ const pool = createPool({ workers: 4 }); // default: CPU count - 1; each worker loads the engine and fonts once
92
+ const pdf = await pool.render(definition, { format: 'pdf', parameters: { id: 42 } }); // Uint8Array
93
+ await pool.close();
94
+ ```
95
+
96
+ Measured on an 8-core laptop, 40 copies of a one-page chart report (`node scripts/bench-pool.mjs`): 1 worker 14.7/s,
97
+ 4 workers 40.7/s.
98
+
99
+ The `pw` command line is in `@reportwright/cli`:
100
+
101
+ ```bash
102
+ npx -p @reportwright/cli pw render statement.pw.json -p accountId=LN-1 -d loans=data.json -f pdf -o statement.pdf
103
+ npx -p @reportwright/cli pw test reports # snapshot tests for reports-as-code
104
+ ```
105
+
106
+ MIT.