@reportwright/cli 0.14.0-rc.1 → 0.14.0-rc.2
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/CHANGELOG.md +11 -1
- package/README.md +6 -0
- package/dist/cli.mjs +57 -16
- package/dist/cli.mjs.map +2 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All `@reportwright/*` packages (formerly `@pagewrightjs/*`) share one version. The versioning policy is in [CONTRIBUTING.md](CONTRIBUTING.md#versioning-semver).
|
|
4
4
|
|
|
5
|
+
## 0.14.0-rc.2 — 2026-10-10 (release candidate)
|
|
6
|
+
|
|
7
|
+
### Fixed (from the rc.1 release gate)
|
|
8
|
+
- **Memory of a zebra table in `exportPdfStream`.** A detail row whose style reads `RowNumber()` (`backgroundColor: '=IIf(RowNumber() Mod 2 = 0, "#f3f4f6", "#ffffff")'`) was refused by the streaming planner ("detail expressions", in `r.why`) and rendered whole, so every page stayed in memory: ~3.5–8 KB a row, 558 MB–1 GB at 100,000 rows. It now streams (row styles may read `RowNumber()`, as row values could), and its pages are the whole render's (pixels, text). Peak RSS at 100,000 rows, probe `scripts/eng-probe.mjs`: plain 270 MB, zebra 1,056 → 287 MB, zebra with expression borders and formats 1,261 → 300 MB.
|
|
9
|
+
- **Expression styles that give the same values share one style.** Each row's evaluated style was a new object, and the cell styles built on it a new cache entry per row (garbage V8 collected late: about 2× the peak RSS of a plain table and 2.5× its time). Rows whose expressions give the same plain values now share one frozen style (at most 64 per style), so a zebra table renders at about the plain table's speed (100,000 rows: 5.8 s → 2.1 s; plain 1.6 s). Reports without expression styles are byte-identical; the zebra reports' bytes change only because they now stream (same pages, pixels and text).
|
|
10
|
+
- The zebra PDF stays larger than the plain one (100,000 rows: 7.4 MB against 3.5 MB): each cell's fill is drawn as a rectangle with its colour (`rg … re f`), as the whole render always drew it; there is no resource per row.
|
|
11
|
+
|
|
12
|
+
### Docs
|
|
13
|
+
- The engine README says when a streamed PDF's memory stays flat, and what "Page N of M" costs (a counting pass: about 1.5–1.8× the time, +10–60 MB at 50,000 rows).
|
|
14
|
+
|
|
5
15
|
## 0.14.0-rc.1 — 2026-10-10 (release candidate)
|
|
6
16
|
|
|
7
17
|
### Changed
|
|
8
|
-
- **PDFs are written by `@reportwright/pdf`** (pinned `0.1.0-beta.
|
|
18
|
+
- **PDFs are written by `@reportwright/pdf`** (pinned `0.1.0-beta.9`, no dependencies of its own), the streaming PDF writer extracted from the engine. `exportPdfStream` (plain, PDF/A, PDF/UA-tagged, `parallel` workers) and `exportPdf` for reports of text (any font, shaped scripts), boxes, ellipses, lines and links go through it. Images, SVG paths, rounded boxes, form fields, encryption, signing (`prepare`), `save` options, and whole-model PDF/A and tagged files stay on pdf-lib for now.
|
|
9
19
|
- **The bytes change; the content does not.** `scripts/writer-parity.mjs` exports every report of the gate corpus (the repository's reports, the streaming ledgers, the 54-report tester corpus) with both writers and requires pixel-identical pages (pdftoppm, 72 dpi, zero differing pixels), the same text per page, `qpdf --check` and veraPDF (PDF/A-2b, 2a, PDF/UA-1) passing, the same fonts (embedded, subset), links, outline, structure elements, form fields, attachments, metadata and page labels, and the same bytes on two runs. One accepted difference: right-to-left locale formats (ar-SA, he-IL) no longer carry their invisible bidi marks (U+200F…) in the extracted text, so a viewer copies "Page 1 of 1" instead of "1 of 1 Page"; the words and pixels are the same.
|
|
10
20
|
- **`exportPdf` of a plain report no longer loads pdf-lib.** A report in embedded fonts (Inter, Noto) exports about twice as fast (one 300-row grouped table: ~110 ms → ~50 ms cold) and with 5–10 MB less memory; its file is about 5% larger (the font subset keeps glyph IDs, as the streaming writer always did).
|
|
11
21
|
- The file ID of a streamed PDF without `id` is now a hash of its content (it was random): the same report and `creationDate` give the same bytes.
|
package/README.md
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
The CLI carries [`@reportwright/engine`](https://github.com/MrArun005/reportwright/tree/main/packages/engine); fonts come from [`@reportwright/fonts`](https://github.com/MrArun005/reportwright/tree/main/packages/fonts).
|
|
10
10
|
|
|
11
|
+
## Screenshots
|
|
12
|
+
|
|
13
|
+
`pw render` output, page 1 (reports from the repository's `reports/`):
|
|
14
|
+
|
|
15
|
+
<img src="https://raw.githubusercontent.com/MrArun005/reportwright/main/docs/screenshots/sales-by-region.png" alt="Grouped table" width="32%"> <img src="https://raw.githubusercontent.com/MrArun005/reportwright/main/docs/screenshots/loan-statement.png" alt="Loan statement" width="32%"> <img src="https://raw.githubusercontent.com/MrArun005/reportwright/main/docs/screenshots/tablix-showcase.png" alt="Pivot tables" width="32%">
|
|
16
|
+
|
|
11
17
|
## Install
|
|
12
18
|
|
|
13
19
|
```bash
|
package/dist/cli.mjs
CHANGED
|
@@ -18831,31 +18831,63 @@ function resolveStyle(layers, ctx) {
|
|
|
18831
18831
|
}
|
|
18832
18832
|
}
|
|
18833
18833
|
if (allStatic) {
|
|
18834
|
-
let
|
|
18834
|
+
let map2 = CACHE;
|
|
18835
18835
|
for (const l of layers) {
|
|
18836
18836
|
if (!l || typeof l !== "object") continue;
|
|
18837
|
-
let next =
|
|
18837
|
+
let next = map2.get(l);
|
|
18838
18838
|
if (!next) {
|
|
18839
18839
|
next = { map: /* @__PURE__ */ new WeakMap(), value: null };
|
|
18840
|
-
|
|
18840
|
+
map2.set(l, next);
|
|
18841
18841
|
}
|
|
18842
18842
|
node3 = next;
|
|
18843
|
-
|
|
18843
|
+
map2 = next.map;
|
|
18844
18844
|
}
|
|
18845
18845
|
if (node3?.value) return node3.value;
|
|
18846
18846
|
}
|
|
18847
18847
|
const out = {};
|
|
18848
|
+
let key = allStatic ? null : "";
|
|
18848
18849
|
for (const l of layers) {
|
|
18849
18850
|
if (!l || typeof l !== "object") continue;
|
|
18850
18851
|
for (const k in l) {
|
|
18851
18852
|
const v = l[k];
|
|
18852
18853
|
if (v === void 0) continue;
|
|
18853
|
-
|
|
18854
|
+
if (!isDynamic(v)) {
|
|
18855
|
+
out[k] = v;
|
|
18856
|
+
continue;
|
|
18857
|
+
}
|
|
18858
|
+
const x = out[k] = safe(v, ctx);
|
|
18859
|
+
if (key === null) continue;
|
|
18860
|
+
const t = typeof x;
|
|
18861
|
+
if (t === "string") key += `${k}${JSON.stringify(x)},`;
|
|
18862
|
+
else if (x == null || t === "boolean" || t === "number" && !Object.is(x, -0)) key += `${k}:${x},`;
|
|
18863
|
+
else key = null;
|
|
18864
|
+
}
|
|
18865
|
+
}
|
|
18866
|
+
if (allStatic) {
|
|
18867
|
+
if (node3) node3.value = Object.freeze(out);
|
|
18868
|
+
return out;
|
|
18869
|
+
}
|
|
18870
|
+
if (key === null) return out;
|
|
18871
|
+
let map = CACHE;
|
|
18872
|
+
for (const l of layers) {
|
|
18873
|
+
if (!l || typeof l !== "object") continue;
|
|
18874
|
+
let next = map.get(l);
|
|
18875
|
+
if (!next) {
|
|
18876
|
+
next = { map: /* @__PURE__ */ new WeakMap(), value: null };
|
|
18877
|
+
map.set(l, next);
|
|
18854
18878
|
}
|
|
18879
|
+
node3 = next;
|
|
18880
|
+
map = next.map;
|
|
18855
18881
|
}
|
|
18856
|
-
if (
|
|
18882
|
+
if (!node3) return out;
|
|
18883
|
+
const kept = node3.dyn ||= /* @__PURE__ */ new Map();
|
|
18884
|
+
const hit = kept.get(key);
|
|
18885
|
+
if (hit) return hit;
|
|
18886
|
+
if (kept.size >= DYN_KEEP) return out;
|
|
18887
|
+
kept.set(key, Object.freeze(out));
|
|
18857
18888
|
return out;
|
|
18858
18889
|
}
|
|
18890
|
+
var DYN_KEEP = 64;
|
|
18859
18891
|
var CACHE = /* @__PURE__ */ new WeakMap();
|
|
18860
18892
|
var DYN = /* @__PURE__ */ new WeakMap();
|
|
18861
18893
|
function hasDynamic(l) {
|
|
@@ -31253,7 +31285,7 @@ function memorySorter(cmp) {
|
|
|
31253
31285
|
};
|
|
31254
31286
|
}
|
|
31255
31287
|
var okValue = (v, o) => typeof v !== "string" || !isDynamic(v) || streamable(v, o);
|
|
31256
|
-
var okStyle = (st, d = 0) => !st || typeof st !== "object" || d > 8 || Object.values(st).every((v) => v && typeof v === "object" ? okStyle(v, d + 1) : okValue(v));
|
|
31288
|
+
var okStyle = (st, o, d = 0) => !st || typeof st !== "object" || d > 8 || Object.values(st).every((v) => v && typeof v === "object" ? okStyle(v, o, d + 1) : okValue(v, o));
|
|
31257
31289
|
var isHidden = (h) => !(h == null || h === false || h === "");
|
|
31258
31290
|
function foldOf(v) {
|
|
31259
31291
|
if (typeof v !== "string" || !v.startsWith("=")) return null;
|
|
@@ -31314,8 +31346,9 @@ async function planStream(def0, opts) {
|
|
|
31314
31346
|
for (const g of groups) if (g.parent || g.sortBy || g.collapsible && g.initiallyCollapsed || g.expandLevels != null || !okValue(g.expr)) return no2("group shape");
|
|
31315
31347
|
const okRow = (row, { rowNumber = false, folds: folds2 = false } = {}) => {
|
|
31316
31348
|
if (!row || isHidden(row.hidden) && row.hidden !== true) return false;
|
|
31317
|
-
|
|
31318
|
-
|
|
31349
|
+
const so = opts.paged ? { rowNumber } : void 0;
|
|
31350
|
+
if (!okStyle(row.style, so)) return false;
|
|
31351
|
+
return (Array.isArray(row.cells) ? row.cells : []).every((c) => !c || !c.item && !(Number(c.rowSpan) > 1) && !("raw" in c) && okStyle(c.style, so) && (okValue(c.value, { rowNumber }) || folds2 && foldOf(c.value)));
|
|
31319
31352
|
};
|
|
31320
31353
|
if (!(item.detail || []).every((r) => okRow(r, { rowNumber: true }))) return no2("detail expressions");
|
|
31321
31354
|
if (!(item.header || []).every((r) => okRow(r))) return no2("header expressions");
|
|
@@ -31829,7 +31862,7 @@ async function planPaged(def0, opts = {}) {
|
|
|
31829
31862
|
const { def, parts } = await resolveReuse(def0, makeLoader(opts.loadReport, /* @__PURE__ */ new Map()), () => {
|
|
31830
31863
|
}, opts.reportId ? [opts.reportId] : [], { items: 0 });
|
|
31831
31864
|
if (parts) return no2("report parts");
|
|
31832
|
-
const plan2 = await planStream(def, { ...opts, format: "xlsx" });
|
|
31865
|
+
const plan2 = await planStream(def, { ...opts, format: "xlsx", paged: true });
|
|
31833
31866
|
if (plan2.why !== void 0) return no2(plan2.why);
|
|
31834
31867
|
if ((def.dataSets || []).length !== 1) return no2("more than one data set");
|
|
31835
31868
|
let ds = def.dataSets[0];
|
|
@@ -33297,7 +33330,10 @@ stream
|
|
|
33297
33330
|
literal(key, text2) {
|
|
33298
33331
|
const F = fontOf(key);
|
|
33299
33332
|
let out = "";
|
|
33300
|
-
const L =
|
|
33333
|
+
const L = (
|
|
33334
|
+
/** @type {any} */
|
|
33335
|
+
F.lat ||= new Array(256)
|
|
33336
|
+
);
|
|
33301
33337
|
let i = 0;
|
|
33302
33338
|
for (; i < text2.length; i++) {
|
|
33303
33339
|
const c = text2.charCodeAt(i);
|
|
@@ -34241,7 +34277,8 @@ async function pdfOf(model, fontStore2, opt) {
|
|
|
34241
34277
|
if (opt.subset && !fieldFonts.has(key) && !fontStore2.get(key)["OS/2"]?.fsType?.noSubsetting) {
|
|
34242
34278
|
const cps = used.get(key) || /* @__PURE__ */ new Set([32]);
|
|
34243
34279
|
try {
|
|
34244
|
-
const sub = withPadding(await
|
|
34280
|
+
const sub = withPadding(await /** @type {any} */
|
|
34281
|
+
opt.subset(bytes, cps, glyphs ? { glyphs: glyphs.keys() } : void 0));
|
|
34245
34282
|
const f4 = fontkit.create(sub);
|
|
34246
34283
|
for (const cp of cps) void f4.glyphForCodePoint(cp).advanceWidth;
|
|
34247
34284
|
for (const id of glyphs?.keys() || []) void f4.getGlyph(id).advanceWidth;
|
|
@@ -34293,7 +34330,9 @@ async function pdfOf(model, fontStore2, opt) {
|
|
|
34293
34330
|
if (why) return { img: null, why: `the PNG is not usable (${why})` };
|
|
34294
34331
|
if (!local) return { img: await doc.embedPng(bytes) };
|
|
34295
34332
|
const e2 = await PngEmbedder.for(bytes);
|
|
34296
|
-
const size = src.length + 2 *
|
|
34333
|
+
const size = src.length + 2 * /** @type {any} */
|
|
34334
|
+
((e2.image.rgbChannel?.length || 0) + /** @type {any} */
|
|
34335
|
+
(e2.image.alphaChannel?.length || 0));
|
|
34297
34336
|
if (size > PNG_CACHE_ENTRY) return { img: await doc.embedPng(bytes) };
|
|
34298
34337
|
const entry = { e: e2, z: (
|
|
34299
34338
|
/** @type {any[]} */
|
|
@@ -34364,6 +34403,7 @@ async function pdfOf(model, fontStore2, opt) {
|
|
|
34364
34403
|
c.lineWidth(1);
|
|
34365
34404
|
draw();
|
|
34366
34405
|
const ops = (
|
|
34406
|
+
/** @type {any} */
|
|
34367
34407
|
/** @type {any} */
|
|
34368
34408
|
p.getContentStream().operators
|
|
34369
34409
|
);
|
|
@@ -36828,7 +36868,7 @@ var THEME = `${XML2}<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingm
|
|
|
36828
36868
|
// package.json
|
|
36829
36869
|
var package_default = {
|
|
36830
36870
|
name: "reportwright",
|
|
36831
|
-
version: "0.14.0-rc.
|
|
36871
|
+
version: "0.14.0-rc.2",
|
|
36832
36872
|
license: "MIT",
|
|
36833
36873
|
private: true,
|
|
36834
36874
|
engines: { node: "22.x" },
|
|
@@ -36856,7 +36896,8 @@ var package_default = {
|
|
|
36856
36896
|
"build:packages": "node scripts/build-embed.mjs && node scripts/build-packages.mjs",
|
|
36857
36897
|
"scan:packages": "node scripts/scan-packages.mjs",
|
|
36858
36898
|
perf: "node scripts/perf.mjs",
|
|
36859
|
-
"build:site": "node scripts/build-site.mjs"
|
|
36899
|
+
"build:site": "node scripts/build-site.mjs",
|
|
36900
|
+
"format:check": "node scripts/format-check.mjs"
|
|
36860
36901
|
},
|
|
36861
36902
|
dependencies: {
|
|
36862
36903
|
"@anthropic-ai/sdk": "^0.131.0",
|
|
@@ -36869,7 +36910,7 @@ var package_default = {
|
|
|
36869
36910
|
"@node-saml/node-saml": "^5.1.0",
|
|
36870
36911
|
"@pdf-lib/fontkit": "^1.1.1",
|
|
36871
36912
|
"@pdf-lib/standard-fonts": "^1.0.0",
|
|
36872
|
-
"@reportwright/pdf": "0.1.0-beta.
|
|
36913
|
+
"@reportwright/pdf": "0.1.0-beta.9",
|
|
36873
36914
|
"@signpdf/placeholder-pdf-lib": "^3.3.0",
|
|
36874
36915
|
"@signpdf/signer-p12": "^3.3.0",
|
|
36875
36916
|
"@signpdf/signpdf": "^3.3.0",
|