quario 0.6.0 → 0.8.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.
- package/CHANGELOG.md +183 -67
- package/README.md +58 -17
- package/lib/format.js +5 -28
- package/lib/image.js +92 -0
- package/lib/index.d.ts +127 -12
- package/lib/index.js +17 -8
- package/lib/license.js +5 -5
- package/lib/math.js +64 -17
- package/lib/memo.js +49 -0
- package/lib/plan.js +385 -73
- package/lib/reducers.js +48 -1
- package/lib/stream.js +74 -25
- package/lib/style.js +50 -3
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,178 @@
|
|
|
1
|
-
#
|
|
1
|
+
# quario
|
|
2
|
+
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **BREAKING: an image whose header does not state its size now fails on every
|
|
8
|
+
target.** A PNG or JPEG truncated before its dimensions used to render on the
|
|
9
|
+
HTML and CSV targets — the browser coped, and CSV has no picture to place —
|
|
10
|
+
while failing on the PDF and XLSX targets, which have to know how big a
|
|
11
|
+
picture is before they can put it anywhere. One file, two verdicts, depending
|
|
12
|
+
on where the report was sent.
|
|
13
|
+
|
|
14
|
+
It is now a single render error, raised where every other verdict on image
|
|
15
|
+
bytes is raised and naming the item's `source` the same way — for example
|
|
16
|
+
`header[0].source [=$.input.logo]: could not read the image's size from its bytes`.
|
|
17
|
+
A report that renders as a PDF today is unaffected: those bytes already
|
|
18
|
+
failed there. What changes is a report that renders as HTML today on bytes no other target
|
|
19
|
+
would accept: it now fails, and the file it names is one no target could ever
|
|
20
|
+
have drawn correctly.
|
|
21
|
+
|
|
22
|
+
Nothing further has changed about what the engine promises: a file whose size
|
|
23
|
+
reads but whose pixel data is corrupt is still nobody's guarantee, and each
|
|
24
|
+
target does whatever its own machinery does with it.
|
|
25
|
+
|
|
26
|
+
- **The image event carries the picture's size.** `width` and `height`, in
|
|
27
|
+
pixels, read from the same header the format is sniffed from. A target — the
|
|
28
|
+
built-in ones, or your own — places a picture from two fields instead of
|
|
29
|
+
parsing a PNG's IHDR and a JPEG's frame header for itself.
|
|
30
|
+
- **A page-number token says which page value it is.** A value token whose
|
|
31
|
+
interpolation is exactly `{{ page.number }}` or `{{ page.total }}` now carries
|
|
32
|
+
`field` on the event stream, holding that same string. The `value` is
|
|
33
|
+
unchanged — the number the page band was called with — so nothing renders
|
|
34
|
+
differently; `field` tells a target whose own document format numbers pages
|
|
35
|
+
that it may write its own live field there instead of the number this render
|
|
36
|
+
saw. Anything computed from them (`{{ page.number + 1 }}`, `{{ pad(page.total)
|
|
37
|
+
}}`) is an ordinary value and carries no `field`, so write the bare token
|
|
38
|
+
wherever a live number matters.
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- **A PNG wider or taller than 65535 pixels is no longer misplaced.** Its
|
|
43
|
+
dimensions were read from the low half of each of IHDR's four-byte fields, so
|
|
44
|
+
a 70000-pixel panorama measured 4464 pixels and was laid out at that size,
|
|
45
|
+
confidently and silently.
|
|
46
|
+
- **Every published README says where the documentation is.** Each package now
|
|
47
|
+
carries a Documentation section pointing at the reference, at the report schema
|
|
48
|
+
that normatively specifies what a report may declare, and at the package's own
|
|
49
|
+
API. The paragraphs that used to end on an unstated contract — the event
|
|
50
|
+
stream's field semantics, the style vocabulary, page columns, the Content
|
|
51
|
+
Security Policy a fragment with images needs, the formula mangling, and each
|
|
52
|
+
target's own contract — link the page that states it. Every link is an absolute
|
|
53
|
+
URL, so it resolves from the npm package page as readily as from an installed
|
|
54
|
+
copy.
|
|
55
|
+
|
|
56
|
+
## 0.7.0
|
|
57
|
+
|
|
58
|
+
### Minor Changes
|
|
59
|
+
|
|
60
|
+
- **Styled runs.** Bold a word, colour a phrase, or format one value inside a
|
|
61
|
+
sentence, and have it survive into the PDF and the spreadsheet — not only the
|
|
62
|
+
HTML, which was the only place `<b>` in a template ever meant anything. A run
|
|
63
|
+
carries the inline declarations only: no padding, borders or spacing, which
|
|
64
|
+
belong to a whole line or block.
|
|
65
|
+
|
|
66
|
+
A cell's tokens carry a run's resolved style on the tokens it covers, and
|
|
67
|
+
`styledRuns(tokens)` groups them back out — the same rule every built-in
|
|
68
|
+
target reads them through, so a custom target cannot drift from them.
|
|
69
|
+
`RUN_STYLE_NAMES` is the inline half of the vocabulary as a list, beside
|
|
70
|
+
`STYLE_NAMES`, for a tool that offers an author what a run may wear.
|
|
71
|
+
|
|
72
|
+
- **`plan()` now reports declarations that nothing will read.** A definition can
|
|
73
|
+
be perfectly valid and still say something the engine quietly drops, and
|
|
74
|
+
until now the only way to find out was to notice the output was wrong.
|
|
75
|
+
`plan()` returns a `warnings` list beside `problems` — same shape, same
|
|
76
|
+
document order, one entry per declaration — and it currently catches three:
|
|
77
|
+
|
|
78
|
+
- a `format` on a cell whose value can never present a single interpolation —
|
|
79
|
+
`"Due {{ due }}"` under `format: "date"` — which the engine leaves unread;
|
|
80
|
+
- a `currency` on a cell whose `format` is not `"currency"`, which the engine
|
|
81
|
+
reads only under that kind and otherwise ignores, so the cell renders in
|
|
82
|
+
the instance's currency and the code you wrote does nothing;
|
|
83
|
+
- a table where every column declares a `width` and the widths total under
|
|
84
|
+
100, which leaves the trailing share of the table unused — usually a column
|
|
85
|
+
edited down without its neighbours being adjusted.
|
|
86
|
+
|
|
87
|
+
`problems` is unchanged and still fatal: a definition with problems compiles
|
|
88
|
+
to nothing, while one carrying only warnings compiles and renders exactly as
|
|
89
|
+
before. Hosts that ignore the new field are unaffected, and `validate()` is
|
|
90
|
+
untouched.
|
|
91
|
+
|
|
92
|
+
Warnings are advisory and deliberately incomplete — they catch what can be
|
|
93
|
+
seen in the definition alone, without data, so a quiet `warnings` list is not
|
|
94
|
+
a promise that every declaration will be read.
|
|
95
|
+
|
|
96
|
+
- **A `format` declaration now needs one value to speak about, and a cell can
|
|
97
|
+
give it one.** Previously a `format` on a cell reached every interpolation in
|
|
98
|
+
it and skipped the literal text between them, which meant
|
|
99
|
+
`"{{ amount }} of {{ quantity }}"` under `format: "currency"` rendered the
|
|
100
|
+
quantity as money too — and the page and the spreadsheet disagreed about it,
|
|
101
|
+
the page formatting inside a sentence where the sheet did not.
|
|
102
|
+
|
|
103
|
+
A cell `value` may now be a list of **styled runs** —
|
|
104
|
+
`{ "value": "…", "style": { … } }` — each with its own declarations, and
|
|
105
|
+
`format` applies to a run holding a single interpolation.
|
|
106
|
+
|
|
107
|
+
**What changes in an existing report:** a cell that mixes text and an
|
|
108
|
+
interpolation under a `format` stops presenting that value and renders it
|
|
109
|
+
plainly, in every target. `"Due {{ due }}"` with `format: "date"` was
|
|
110
|
+
`Due 14 Aug 2026` on the page and `Due 2026-08-14` in the sheet; it is now
|
|
111
|
+
the plain form in both. To restore it, split the value and leave the
|
|
112
|
+
declaration where it is:
|
|
2
113
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## [0.6.0] - 2026-09-07
|
|
114
|
+
```jsonc
|
|
115
|
+
{
|
|
116
|
+
"value": [{ "value": "Due " }, { "value": "{{ due }}" }],
|
|
117
|
+
"style": { "format": "date" }
|
|
118
|
+
}
|
|
119
|
+
```
|
|
11
120
|
|
|
12
|
-
|
|
121
|
+
A cell's `format` reaches its runs, so nothing moves and nothing is declared
|
|
122
|
+
twice. `plan()` reports the cells this affects wherever it can see them
|
|
123
|
+
without data.
|
|
124
|
+
|
|
125
|
+
- **`round`, `floor` and `ceil` reuse their formatters instead of rebuilding
|
|
126
|
+
one per call.** The three reach the decimal you wrote through `Intl`, and
|
|
127
|
+
each call built a fresh formatter to do it — so a detail column of
|
|
128
|
+
`{{ round(@.qty * @.price, 2) }}` paid for one per row. A hundred-thousand
|
|
129
|
+
row report with two such columns drained in 3.5 s; it now drains in 0.2 s.
|
|
130
|
+
Every answer is the value it was before: the rounding mode and the digit
|
|
131
|
+
count are both part of what a reused formatter is found by, so no call can
|
|
132
|
+
be handed one built for another.
|
|
133
|
+
- **`round`, `floor` and `ceil` name themselves when they refuse an `n`.** All
|
|
134
|
+
three threw `n must be a number from 0 to 100`, so a cell calling more than
|
|
135
|
+
one was told how it failed and never which call; the message now opens with
|
|
136
|
+
the function, as a reducer's already did. The located path still names the
|
|
137
|
+
cell — one cell holds as many calls as you write, which is the whole reason
|
|
138
|
+
the name is worth having.
|
|
139
|
+
|
|
140
|
+
**An `n` that cannot be read as a number at all now gets the same answer.**
|
|
141
|
+
A `BigInt` or a `Symbol` reaching `n` from render data used to surface the
|
|
142
|
+
runtime's own `Cannot convert a BigInt value to a number`, and an object
|
|
143
|
+
carrying a throwing `valueOf` surfaced whatever it threw — each located at
|
|
144
|
+
the cell but naming neither the function nor anything an author could act
|
|
145
|
+
on. All of them are now the same refusal.
|
|
146
|
+
|
|
147
|
+
- **`isDiagnostic` is documented for what it actually answers.** The README
|
|
148
|
+
said it was true for "one of the stack's located errors: quario's own, or one
|
|
149
|
+
thrown directly by xprsn, sjabloon, or padvinder", and its example read a
|
|
150
|
+
`false` as "one of your own functions failed". Only an engine's own error is
|
|
151
|
+
a diagnostic: quario's verdicts on a document — an unknown reducer, a
|
|
152
|
+
misused inline reducer, image bytes it will not vouch for — are located just
|
|
153
|
+
the same and are not diagnostics, so a host following that example rethrew
|
|
154
|
+
report faults as its own. Nothing about the guard changes; it never behaved
|
|
155
|
+
the way the page described.
|
|
156
|
+
- **A misused inline reducer says what it is, instead of how it folds.**
|
|
157
|
+
`{{ min(1, 2) }}` — the two-argument scalar `min` quario does not have —
|
|
158
|
+
failed at the cell with `(rows || []).map is not a function`, the fold's own
|
|
159
|
+
internals, naming neither the reducer nor the mistake. It now reads
|
|
160
|
+
`min is a reducer over an array, not a scalar function; got a number`, and a
|
|
161
|
+
second argument that is not a lambda says so in the same words rather than
|
|
162
|
+
reaching a host as `of is not a function`. Register a function of your own to
|
|
163
|
+
shadow a built-in reducer when a report needs the scalar.
|
|
164
|
+
|
|
165
|
+
**Two reducers used to answer instead of failing**, each reading a `length`
|
|
166
|
+
off a value that is not a collection: `count('ab')` presented `2` — a
|
|
167
|
+
string's own length, folded as if it were a count of rows — while `count(1)`
|
|
168
|
+
and `avg(1, 2)` presented nothing at all. All three are now the same located
|
|
169
|
+
error. A reducer over an **absent** array is unchanged and still not a
|
|
170
|
+
mistake: `sum(@.lines)` on a row carrying no lines is `0`, exactly as an
|
|
171
|
+
empty array gives.
|
|
172
|
+
|
|
173
|
+
## 0.6.0
|
|
174
|
+
|
|
175
|
+
### Minor Changes
|
|
13
176
|
|
|
14
177
|
- **`imageError(path, said, cause)`**, the mint a render target raises an image
|
|
15
178
|
failure through. The engine vouches for an image's magic numbers and no
|
|
@@ -18,7 +181,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
18
181
|
that asked for the bytes and keeps the failing class and message behind it,
|
|
19
182
|
so one report reads the same however it is rendered. It is not a diagnostic:
|
|
20
183
|
`isDiagnostic` does not answer for one.
|
|
21
|
-
|
|
22
184
|
- **Formatted cells render dramatically faster.** The engine built a fresh
|
|
23
185
|
`Intl` formatter for every presented cell and now keeps them, keyed on
|
|
24
186
|
everything a formatter is built from — the kind, the locale, the digit count,
|
|
@@ -74,14 +236,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
74
236
|
|
|
75
237
|
- **`sort` is stable**: rows whose keys compare equal keep the order they
|
|
76
238
|
arrived in. The engine has always sorted this way; the promise is new.
|
|
77
|
-
|
|
78
239
|
- `currencyOf(style, options)` joins the package's exports beside `format()`.
|
|
79
240
|
It answers which currency code a cell wears — its own when it declares one,
|
|
80
241
|
else the instance default — for a target that needs the code itself rather
|
|
81
242
|
than presented text. A cell that declared a code the engine could not accept
|
|
82
243
|
carries `style.currency` as `null`, so `style.currency ?? yourDefault` is the
|
|
83
244
|
wrong spelling and this helper is the right one.
|
|
84
|
-
|
|
85
245
|
- **A cell may name its own currency.** `currency` is a new style declaration
|
|
86
246
|
beside `format` — a three-letter ISO 4217 code saying what a money cell is
|
|
87
247
|
denominated in.
|
|
@@ -134,14 +294,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
134
294
|
- `split-start` events carry `path`, the split definition's schema path, as
|
|
135
295
|
`item` and `image` events already do. A consumer can now name the definition
|
|
136
296
|
behind a split without tracking position.
|
|
137
|
-
|
|
138
297
|
- `fractionDigits(kind, options)` is exported beside `format()`: how many
|
|
139
298
|
fraction digits a kind presents, or nothing for a kind that carries no
|
|
140
299
|
count. It is the table the official targets read, and it is public so a
|
|
141
300
|
consumer writing its own target presents the same digits they do.
|
|
142
|
-
|
|
143
|
-
### Changed
|
|
144
|
-
|
|
145
301
|
- **A `date` cell with no `form` now presents `medium`, not the runtime's own
|
|
146
302
|
default.** A `date` that names no form used to render whatever the platform's
|
|
147
303
|
default date format was — under `en-US`, `8/14/2026` — which matched none of
|
|
@@ -162,18 +318,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
162
318
|
to write no number format. Read `style.format.kind` where you read
|
|
163
319
|
`style.format`, and take the digit count off the declaration rather than
|
|
164
320
|
computing one.
|
|
165
|
-
|
|
166
321
|
- **`fractionDigits` takes the whole style.** Its first argument was the kind
|
|
167
322
|
and is now the style, matching `format()` and `currencyOf()`.
|
|
168
323
|
`fractionDigits("number")` becomes
|
|
169
324
|
`fractionDigits({ format: "number" })`. Targets no longer need it at all —
|
|
170
325
|
the count rides on the declaration the stream carries.
|
|
171
|
-
|
|
172
326
|
- **`typed()`'s second argument is the resolved declaration.** Pass
|
|
173
327
|
`style.format` as it arrives on the stream; it reads the kind from the
|
|
174
328
|
object. The shorthand string is still accepted, and omitting the argument is
|
|
175
329
|
unchanged.
|
|
176
|
-
|
|
177
330
|
- **A rejected box value is now dropped from the resolved style rather than
|
|
178
331
|
carried on it.** A `border*` or `padding*` declaration written as an `=`
|
|
179
332
|
expression used to cross the render stream with whatever the expression
|
|
@@ -312,23 +465,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
312
465
|
- A `currency` code that is not a readable currency now presents nothing in
|
|
313
466
|
every target, rather than the worksheet inventing a number format from it
|
|
314
467
|
while the other targets fell back to the unformatted value.
|
|
315
|
-
|
|
316
|
-
### Fixed
|
|
317
|
-
|
|
318
468
|
- **A split slot is typed with the declarations the traversal accepts.**
|
|
319
469
|
`SplitSlot` reused the band items' style types, so TypeScript refused
|
|
320
470
|
`valign` on an image slot — legal there, and only there — and admitted
|
|
321
471
|
`spaceBefore`/`spaceAfter` on a slot, which the engine rejects. Two new
|
|
322
472
|
exported types, `SlotStyleDeclarations` and `SlotImageStyleDeclarations`,
|
|
323
473
|
now say what a slot may declare.
|
|
324
|
-
|
|
325
474
|
- **A page band closure is typed as what it returns.** `PageBandRenderers`
|
|
326
475
|
declared `header`/`footer` as returning item and image events only, while a
|
|
327
476
|
split in a page band has always come back as its bracket — `split-start`,
|
|
328
477
|
one event per slot, `split-end`. The closures now return `PageBandEvent[]`,
|
|
329
478
|
a new exported union naming all four, so a TypeScript host reading a page
|
|
330
479
|
band no longer types `role` onto a `split-end` that carries none.
|
|
331
|
-
|
|
332
480
|
- **A computed style value the schema does not accept now leaves the layer
|
|
333
481
|
below in place**, which is what the schema has always said it does —
|
|
334
482
|
"contributes nothing, same as omit". The name is dropped from the resolved
|
|
@@ -363,7 +511,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
363
511
|
the cell falls back to display text, as the documented rule says any
|
|
364
512
|
unrecognised kind does. Before, such a cell could render `[object Undefined]`.
|
|
365
513
|
A literal was never affected: it is checked against the four kinds.
|
|
366
|
-
|
|
367
514
|
- **A pinned report header now refuses a lead on every band that can sit under
|
|
368
515
|
its box, and only where the document settles which one that is.** When
|
|
369
516
|
`header` declares a `height`, an authored `spaceBefore` on the first item of
|
|
@@ -393,9 +540,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
393
540
|
than reporting against the report header, which is a band this rule never
|
|
394
541
|
covers.
|
|
395
542
|
|
|
396
|
-
##
|
|
543
|
+
## 0.5.0
|
|
397
544
|
|
|
398
|
-
###
|
|
545
|
+
### Minor Changes
|
|
399
546
|
|
|
400
547
|
- **`STYLE_NAMES`**, every name in the style vocabulary as a read-only list,
|
|
401
548
|
in the order the specification's table lists them. A tool that offers the
|
|
@@ -417,9 +564,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
417
564
|
on a band image, whose boxes have no such slack. A row's or a split's layers
|
|
418
565
|
under its cells' or slots' own. Undeclared is not a declaration: each target
|
|
419
566
|
keeps its own default.
|
|
420
|
-
|
|
421
|
-
### Changed
|
|
422
|
-
|
|
423
567
|
- **A table row's `style` now resolves onto that row's cells**, the box
|
|
424
568
|
included, instead of meaning something different on every output. Before,
|
|
425
569
|
padding and border on `detail.header`, `detail.row` or a total row's `style`
|
|
@@ -442,9 +586,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
442
586
|
carries only what layers by ordinary means, and a row whose whole block was
|
|
443
587
|
box carries no `style` at all.
|
|
444
588
|
|
|
445
|
-
##
|
|
589
|
+
## 0.4.0
|
|
446
590
|
|
|
447
|
-
###
|
|
591
|
+
### Minor Changes
|
|
448
592
|
|
|
449
593
|
- **`format: "date"` now reads a date string, not only a `Date`.** A JSON
|
|
450
594
|
document has no date type, so the kind could not be reached from parsed
|
|
@@ -468,9 +612,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
468
612
|
- **`typed(tokens, kind?)` takes the cell's `format` kind.** Passing `"date"`
|
|
469
613
|
opts into the same revival, for consumers that write typed cells. The
|
|
470
614
|
one-argument call is unchanged.
|
|
471
|
-
|
|
472
|
-
### Fixed
|
|
473
|
-
|
|
474
615
|
- **The TypeScript declarations accept the box.** The per-side `padding*` and
|
|
475
616
|
`border*` names, and the table's `detail.header`, were validated by the
|
|
476
617
|
engine from 0.3.0 but were missing from the shipped declarations, so a
|
|
@@ -485,9 +626,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
485
626
|
about rendering changes: a report that compiled through a cast produces the
|
|
486
627
|
same output without one.
|
|
487
628
|
|
|
488
|
-
##
|
|
629
|
+
## 0.3.0
|
|
489
630
|
|
|
490
|
-
###
|
|
631
|
+
### Minor Changes
|
|
491
632
|
|
|
492
633
|
- **`format` is a closed style name.** `number` / `currency` / `percent` /
|
|
493
634
|
`date` on text items, column cells, headers, and totals. Not images, not
|
|
@@ -495,19 +636,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
495
636
|
live on `quario({ locale, currency, timeZone })`. The public `format()`
|
|
496
637
|
helper is how targets present a kind; a kind on the wrong type contributes
|
|
497
638
|
nothing.
|
|
498
|
-
|
|
499
639
|
- **The report header may pin a `height` from the page top.** Dual-shaped
|
|
500
640
|
like `detail`: an item array, or `{ height, items }`. `page.margin` is a
|
|
501
641
|
document field (one number, all four sides), required with `height` and
|
|
502
642
|
legal without. Authored `spaceBefore` on the first occupying item of the
|
|
503
643
|
next band is refused.
|
|
504
|
-
|
|
505
644
|
- **`spaceBefore` / `spaceAfter` return as item flow spacing.** Blank space
|
|
506
645
|
before or after a band item, in points, including band images and splits as
|
|
507
646
|
band items. Adjacent gaps add. Table cells, `row.style`, headers, totals,
|
|
508
647
|
and split slots refuse the names. `spaceBefore` drops at a fresh body page
|
|
509
648
|
or strip top; page-band items keep it. Leading and inset stay cut.
|
|
510
|
-
|
|
511
649
|
- **Per-side padding and border on the closed style vocabulary.**
|
|
512
650
|
`paddingTop` / `Right` / `Bottom` / `Left` (points, ≥ 0) and, per side,
|
|
513
651
|
`border*Width`, `border*Style` (`solid` | `dashed` | `dotted`),
|
|
@@ -517,18 +655,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
517
655
|
than a solid black stroke. The names are legal wherever `background` is,
|
|
518
656
|
including images, plus `row.style`. The report default still takes only
|
|
519
657
|
`family` and `size`. Column `%` widths are border-box.
|
|
520
|
-
|
|
521
658
|
- **`detail.header` is the header-row box.** `{ style }` only, a different
|
|
522
659
|
path from `columns[i].header`. It crosses the seam as `table-start.style`.
|
|
523
|
-
|
|
524
|
-
### Changed
|
|
525
|
-
|
|
526
660
|
- **A table total is N rows.** Before, `total` was one row: a flat cell
|
|
527
661
|
array or `{ style, cells }`. After, it is absent or a non-empty array of
|
|
528
662
|
`{ cells, style?, visible? }`. A one-row total is `[{ cells: [...] }]`.
|
|
529
663
|
`total: []` is a definition error. Paths are `detail.total[r].cells[i]`.
|
|
530
664
|
The stream yields one `total-row` per emitted row.
|
|
531
|
-
|
|
532
665
|
- **A visible text item occupies a line at its own `size`.** Empty display
|
|
533
666
|
used to take the report default's leading in PDF and collapse in HTML;
|
|
534
667
|
`"a\n\nb"` broke in PDF and collapsed to a space in HTML. A visible item
|
|
@@ -537,9 +670,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
537
670
|
table cells stay contentless for height. This is not a spacing primitive;
|
|
538
671
|
`visible: false` is still how an item leaves the layout.
|
|
539
672
|
|
|
540
|
-
##
|
|
673
|
+
## 0.2.0
|
|
541
674
|
|
|
542
|
-
###
|
|
675
|
+
### Minor Changes
|
|
543
676
|
|
|
544
677
|
- **A report default: one `style` block for the whole document.** A top-level
|
|
545
678
|
`style` beside `header`/`detail`/`footer` states the typeface a report is set
|
|
@@ -549,21 +682,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
549
682
|
a text declaration on an image item is. Values are literals or `=`
|
|
550
683
|
expressions like any other style block's, resolved once per render in report
|
|
551
684
|
scope.
|
|
552
|
-
|
|
553
685
|
- **`report-start` carries the resolved report default as `style`.** A
|
|
554
686
|
report-level fact, never merged into an item's own `style`: an event's
|
|
555
687
|
`style` stays what the author wrote on that node, and a consumer composes the
|
|
556
688
|
default itself, once — under its own band-role defaults and under every
|
|
557
689
|
event's own style. Absent when the report declares none, so a consumer
|
|
558
690
|
written before this field renders in its own baseline exactly as it did.
|
|
559
|
-
|
|
560
691
|
- **An `uppercase` style declaration.** A boolean beside `bold` and `italic`,
|
|
561
692
|
literal or an `=` expression, for the capitalised column labels business
|
|
562
693
|
forms are usually set with. It is capitals, not small caps — real small caps
|
|
563
694
|
need a font feature the PDF target's built-in faces cannot supply, so the
|
|
564
695
|
declaration promises only what every target can draw. Image items keep
|
|
565
696
|
refusing it, as they refuse every text declaration.
|
|
566
|
-
|
|
567
697
|
- **A `split` item places values across the line instead of down the band.**
|
|
568
698
|
The invoice header's "seller left, customer right", which a band could not
|
|
569
699
|
say before. `{ "type": "split", "slots": [...] }` takes two or more slots,
|
|
@@ -574,13 +704,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
574
704
|
a slot that renders nothing keeps its width, so a line's geometry never
|
|
575
705
|
moves with the data. Splits may appear in every item array except table and
|
|
576
706
|
total cells.
|
|
577
|
-
|
|
578
707
|
- **`split-start` / `split-end` bracket a split's slots on the event stream.**
|
|
579
708
|
`split-start` carries the slot geometry, then one ordinary `item` or `image`
|
|
580
709
|
event per slot in order, then `split-end`. Existing consumers need no
|
|
581
710
|
change: the walk driver's missing-handler rule means a target that ignores
|
|
582
711
|
the bracket still receives the slot items and renders them stacked.
|
|
583
|
-
|
|
584
712
|
- **`quario().plan(schema, funcs?)` hands the whole traversal over at once.**
|
|
585
713
|
Returns `{ report, problems, anchors }` from one descent: the compiled
|
|
586
714
|
report (`null` while the document has problems), every problem as
|
|
@@ -604,12 +732,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
604
732
|
display rule behind the token join, re-exported beside `text()` so a stream
|
|
605
733
|
consumer that stringifies token values itself renders exactly what the
|
|
606
734
|
official targets render — Dates included.
|
|
607
|
-
|
|
608
735
|
- **`maxDepth: Infinity` opts a query budget out.** The data query's traversal
|
|
609
736
|
budgets accept an explicit `Infinity` per key for "this budget, unbounded".
|
|
610
737
|
The 500-deep default is unchanged — it is now padvinder's own, applied for
|
|
611
738
|
every consumer rather than added by quario at the seam.
|
|
612
|
-
|
|
613
739
|
- **Located data-query errors carry padvinder's code and span.** A `data`
|
|
614
740
|
query that does not parse now surfaces with padvinder's `code` and
|
|
615
741
|
`start`/`end` offsets into the query you wrote — filter faults included —
|
|
@@ -619,23 +745,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
619
745
|
`PADVINDER_UNKNOWN_FUNCTION`, or `PADVINDER_SYNTAX` for a path character or
|
|
620
746
|
filter body that is open-endedly not a query. Traversal budgets exceeded at
|
|
621
747
|
render time keep `limit`/`actual` and carry no span.
|
|
622
|
-
|
|
623
|
-
### Changed
|
|
624
|
-
|
|
625
748
|
- **Cells render straight over the engine scope chain.** A text cell no longer
|
|
626
749
|
allocates a wrapper scope and an anchor pair per cell per row — quario's
|
|
627
750
|
chain already binds `$` at the render base and `@` on the detail row, and
|
|
628
751
|
sjabloon now renders over it as-is. A 4-column stream over a million rows
|
|
629
752
|
went from 1.7s to 0.5s. No report changes what it renders. Requires
|
|
630
753
|
sjabloon 0.11.
|
|
631
|
-
|
|
632
754
|
- **A compiled report's `functions` carry signatures.** Each entry is now
|
|
633
755
|
`{ name, arity, doc? }` instead of a bare name — `arity` from the function's
|
|
634
756
|
declared parameter count (or its own numeric `arity` where rest parameters
|
|
635
757
|
mislead `length`), `doc` from an own `doc` string when it carries one — in
|
|
636
758
|
the same call-first-seen order. `names` is unchanged. Requires xprsn 0.11
|
|
637
759
|
and sjabloon 0.11.
|
|
638
|
-
|
|
639
760
|
- **A bare `Date` renders as ISO 8601 UTC, the same on every machine.**
|
|
640
761
|
Display text for a `Date` value was `String(date)`, which bakes the
|
|
641
762
|
rendering host's timezone and locale into the output — so one report
|
|
@@ -644,7 +765,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
644
765
|
rule; an invalid `Date` keeps its deterministic `Invalid Date` text.
|
|
645
766
|
Reports that want a formatted date keep using a registered function,
|
|
646
767
|
exactly as before.
|
|
647
|
-
|
|
648
768
|
- **A bad literal pattern in the data query is a definition error.** A typo'd
|
|
649
769
|
I-Regexp written as a string literal in `match()`/`search()` used to
|
|
650
770
|
produce a plausible empty report with no signal; `report()` and
|
|
@@ -656,7 +776,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
656
776
|
fault in an expression: the engine that decided the fault names it. A
|
|
657
777
|
pattern that arrives from render data keeps RFC 9535 semantics and still
|
|
658
778
|
matches nothing at render time. Requires padvinder 0.8.
|
|
659
|
-
|
|
660
779
|
- **Each engine relocates its own diagnostic.** A located error is now a copy
|
|
661
780
|
made by the engine that raised it (xprsn, sjabloon, or padvinder), so it
|
|
662
781
|
carries every field that engine puts on a diagnostic — nothing is lost in
|
|
@@ -664,9 +783,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
664
783
|
passes that engine's own `isDiagnostic`. Host errors are wrapped as plain
|
|
665
784
|
errors with no diagnostic metadata, exactly as before. Requires xprsn 0.10,
|
|
666
785
|
sjabloon 0.9, and padvinder 0.5. No report changes what it renders.
|
|
667
|
-
|
|
668
|
-
### Fixed
|
|
669
|
-
|
|
670
786
|
- **A host error cannot pose as the report's diagnostic.** `report()` rethrows
|
|
671
787
|
the first definition problem's located engine error; it chose that error by
|
|
672
788
|
probing for a `code` property, so a host error class that stamps `code` on
|
|
@@ -675,9 +791,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
675
791
|
`isDiagnostic`; everything else falls back to a plain `SyntaxError` naming
|
|
676
792
|
the first problem, as before.
|
|
677
793
|
|
|
678
|
-
##
|
|
794
|
+
## 0.1.0
|
|
679
795
|
|
|
680
|
-
###
|
|
796
|
+
### Minor Changes
|
|
681
797
|
|
|
682
798
|
- **A compile-once report engine.** You hand it a JSON report definition and
|
|
683
799
|
data; it plans once and streams render events that every target consumes.
|
package/README.md
CHANGED
|
@@ -98,19 +98,28 @@ row, one `total-row` per emitted total row, and `table-end`.
|
|
|
98
98
|
| `table-end` | - |
|
|
99
99
|
| `report-end` | - |
|
|
100
100
|
|
|
101
|
+
The full field semantics are in the [event stream reference](https://getquario.com/docs/reference/events/).
|
|
102
|
+
|
|
101
103
|
**Cells carry tokens.** A cell is `{ tokens, style? }`. Each token is either `{ literal }`
|
|
102
104
|
(static template text, verbatim) or `{ value }` (one interpolation's _pre-format_ value, the
|
|
103
105
|
expression result before any stringification). This is the typed seam: a cell whose template is `{{ @.amount }}` holds one value token with the
|
|
104
106
|
number itself, so a spreadsheet consumer writes a real numeric cell. `Total: {{ @.amount }}` mixes
|
|
105
107
|
a literal and a value, so the cell is text only.
|
|
106
108
|
|
|
109
|
+
**A page-number token names itself.** A value token whose interpolation is exactly
|
|
110
|
+
`{{ page.number }}` or `{{ page.total }}` also carries `field`, holding that same string. The
|
|
111
|
+
value is the number this page was rendered with; `field` says which page value the token stands
|
|
112
|
+
for, so a target whose own document format numbers pages can write its own live field there
|
|
113
|
+
instead. Anything computed from them carries no `field`.
|
|
114
|
+
|
|
107
115
|
**Escaping is the consumer's job.** A target that embeds values in markup must escape them at its
|
|
108
116
|
own edge.
|
|
109
117
|
|
|
110
118
|
**`report-start.marking`** carries the evaluation wording when the render is unlicensed, or while
|
|
111
119
|
verification is still settling. Licensed streams omit it. Targets place the marking; they do not
|
|
112
120
|
author its wording. **`columns`** on `report-start` / `group-start` is the declared
|
|
113
|
-
page column
|
|
121
|
+
[page column](https://getquario.com/docs/diving-deeper/bands/#flowing-in-columns) count when
|
|
122
|
+
present. `@quario/pdf` and `@quario/html` lay page columns out;
|
|
114
123
|
xlsx never will.
|
|
115
124
|
|
|
116
125
|
### `text(tokens)`
|
|
@@ -127,6 +136,14 @@ display text. Passing the cell's resolved `format` kind opts into the seam's one
|
|
|
127
136
|
real numeric cells;
|
|
128
137
|
[`@quario/csv`](https://www.npmjs.com/package/@quario/csv) is the short form.
|
|
129
138
|
|
|
139
|
+
### `styledRuns(tokens)`
|
|
140
|
+
|
|
141
|
+
Groups a cell's tokens into its **styled runs**, in order — each `{ style, tokens }`,
|
|
142
|
+
with `style` `null` where the tokens carry none and the cell's own applies. Consecutive tokens
|
|
143
|
+
with equal styles are one run, which is lossless: equal styles render identically, so the grouping
|
|
144
|
+
survives a JSON round trip. Every built-in target reads a cell's runs through this, so a custom
|
|
145
|
+
target cannot drift from them.
|
|
146
|
+
|
|
130
147
|
### `walk(events, handlers)` / `breathe()`
|
|
131
148
|
|
|
132
149
|
`walk` is the delivery driver every official target uses. Pass one render's event iterable and
|
|
@@ -154,7 +171,8 @@ cell the same way:
|
|
|
154
171
|
- `currencyOf(style?, options?)` — which code a money cell wears: its own, else the instance's.
|
|
155
172
|
- `isReportBand(role)` — whether a role names one of the report's own bands rather than a
|
|
156
173
|
group's.
|
|
157
|
-
- `STYLE_NAMES` — the closed style vocabulary, in the spec's order
|
|
174
|
+
- `STYLE_NAMES` — the closed style vocabulary, in the spec's order, and
|
|
175
|
+
`RUN_STYLE_NAMES` — the inline half of it, which is what a styled run may wear.
|
|
158
176
|
|
|
159
177
|
### `validate(schema, functions?)`
|
|
160
178
|
|
|
@@ -172,34 +190,49 @@ so `validate()` can never disagree with what `report()` accepts.
|
|
|
172
190
|
### `quario().plan(schema, functions?)`
|
|
173
191
|
|
|
174
192
|
The one traversal, whole — for hosts that validate and render in a loop, like an editor. Returns
|
|
175
|
-
`{ report, problems, anchors }`: the compiled report (`null` while the document has
|
|
176
|
-
every problem structurally as `{ path, source?, message, diagnostic? }` (the `message`
|
|
177
|
-
`validate()`'s string, and every problem keeps its own located diagnostic with
|
|
178
|
-
offsets, not only the first),
|
|
179
|
-
anchors and group handles it reads — the unfiltered complement of `names
|
|
193
|
+
`{ report, problems, anchors, warnings }`: the compiled report (`null` while the document has
|
|
194
|
+
problems), every problem structurally as `{ path, source?, message, diagnostic? }` (the `message`
|
|
195
|
+
is exactly `validate()`'s string, and every problem keeps its own located diagnostic with
|
|
196
|
+
`start`/`end` offsets, not only the first), `anchors`, mapping each compiled source's schema path
|
|
197
|
+
to the anchors and group handles it reads — the unfiltered complement of `names` — and
|
|
198
|
+
`warnings`.
|
|
199
|
+
|
|
200
|
+
A warning is `{ path, source?, message }`: the document declares something nothing will read. It
|
|
201
|
+
is not a problem at a lower severity, which is why it has no `diagnostic` — nothing raised, the
|
|
202
|
+
engine decided. Neither warning below locates into an authored source, so none carries a `source`
|
|
203
|
+
today. **A warning is never fatal**: a document carrying only warnings compiles and
|
|
204
|
+
renders, so `report` is `null` on `problems` alone. Two declarations warn today — a `currency` on
|
|
205
|
+
a cell whose `format` is not `"currency"`, and a table where every column is sized and the widths
|
|
206
|
+
total under 100 — and the list is advisory and deliberately incomplete, so a quiet one is not a
|
|
207
|
+
promise that every declaration will be read. `validate()` returns problems only.
|
|
180
208
|
|
|
181
209
|
```js
|
|
182
|
-
const { report, problems, anchors } = quario().plan(schema);
|
|
210
|
+
const { report, problems, anchors, warnings } = quario().plan(schema);
|
|
211
|
+
for (const warning of warnings) console.warn(warning.message);
|
|
183
212
|
if (report) await report.render(html(), data);
|
|
184
213
|
else console.error(problems[0].path, problems[0].message);
|
|
185
214
|
```
|
|
186
215
|
|
|
187
216
|
### `isDiagnostic(error)`
|
|
188
217
|
|
|
189
|
-
True when a caught value is
|
|
190
|
-
|
|
191
|
-
matches the shape does not pass.
|
|
218
|
+
True when a caught value is a **located diagnostic**: an error xprsn, sjabloon or padvinder
|
|
219
|
+
minted — thrown by that engine, or re-thrown by quario with the engine original behind it.
|
|
220
|
+
Authentication checks identity. An error that only matches the shape does not pass.
|
|
192
221
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
222
|
+
Being located is not what the guard reads: quario's own verdicts on a document and a registered
|
|
223
|
+
function's own throw are located too, and neither is a diagnostic. Errors a report throws
|
|
224
|
+
name the path they failed at — and the offending source, where there is one — while keeping their
|
|
225
|
+
original type (`SyntaxError`, `TypeError`, `RangeError`). What a diagnostic adds on top is
|
|
226
|
+
metadata an engine vouches for: `code`, `start`/`end` offsets, and, for a query budget in place
|
|
227
|
+
of those offsets, `limit` and `actual`.
|
|
196
228
|
|
|
197
229
|
```js
|
|
198
230
|
try {
|
|
199
231
|
await report.render(html(), data);
|
|
200
232
|
} catch (e) {
|
|
201
|
-
|
|
202
|
-
|
|
233
|
+
// Every error names where it failed; a diagnostic also carries an engine's own metadata.
|
|
234
|
+
if (isDiagnostic(e)) console.error(e.code, e.start, e.end);
|
|
235
|
+
throw e;
|
|
203
236
|
}
|
|
204
237
|
```
|
|
205
238
|
|
|
@@ -232,7 +265,8 @@ Markdown target lives in the repository at `example/markdown.js` in about 70 lin
|
|
|
232
265
|
targets are written against the same public API.
|
|
233
266
|
|
|
234
267
|
Two rules a target owes its users: escape or neutralize every `value` token at your own edge, and
|
|
235
|
-
map the style vocabulary to your own
|
|
268
|
+
map the [style vocabulary](https://getquario.com/docs/reference/style-declarations/) to your own
|
|
269
|
+
formatting model rather than expecting CSS.
|
|
236
270
|
|
|
237
271
|
The stream is additive. The walk driver skips any event you register no handler for, so a target
|
|
238
272
|
that ignores a newer event (for example, `example/markdown.js` has none for `image`) keeps rendering
|
|
@@ -248,6 +282,13 @@ package, so it runs under a script policy that omits `unsafe-eval`. The test sui
|
|
|
248
282
|
under Node's `--disallow-code-generation-from-strings` flag, a source scan, and a Playwright
|
|
249
283
|
harness that loads the published files under a strict CSP.
|
|
250
284
|
|
|
285
|
+
## Documentation
|
|
286
|
+
|
|
287
|
+
[The quario documentation](https://getquario.com/docs/) is the reference.
|
|
288
|
+
The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
|
|
289
|
+
specification of what a report may declare, and
|
|
290
|
+
the [engine reference](https://getquario.com/docs/reference/quario/) is this package's own API.
|
|
291
|
+
|
|
251
292
|
## License
|
|
252
293
|
|
|
253
294
|
Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
|