quario 0.8.0 → 0.9.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 +44 -0
- package/README.md +27 -27
- package/lib/host.js +89 -0
- package/lib/index.d.ts +27 -0
- package/lib/index.js +1 -0
- package/lib/license.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# quario
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **Every target factory now refuses an option it does not understand.** An unknown key, a key with
|
|
8
|
+
a value of the wrong type, or an `options` that is not an object throws a `TypeError` at the
|
|
9
|
+
factory call, naming the option path — `options.meta.title`, `options.page.size`. `@quario/docx`
|
|
10
|
+
already behaved this way. `@quario/pdf`, `@quario/html`, `@quario/xlsx` and `@quario/layout` read
|
|
11
|
+
the keys they knew and ignored the rest, so a typo cost you the option you meant to set with no
|
|
12
|
+
signal at any point. `csv()` takes no options, and it refuses an argument rather than discarding
|
|
13
|
+
one.
|
|
14
|
+
|
|
15
|
+
The check itself is one thing, so the engine now exports it: `hostOptions` closes a key set and
|
|
16
|
+
`hostMeta` validates the `{ title, author, subject }` contract the document targets share. A
|
|
17
|
+
fourth document property is one edit rather than three.
|
|
18
|
+
|
|
19
|
+
**What this changes for you.** One options object spread across several targets stops working if
|
|
20
|
+
any target does not know one of its keys — `const o = { page, fonts, meta }` passed to `pdf(o)`,
|
|
21
|
+
`html(o)` and `xlsx(o)` is three different key sets. An object holding only what its consumers
|
|
22
|
+
share is unaffected, so `{ page, fonts }` into both `pdf()` and `layout()` still works.
|
|
23
|
+
TypeScript does not warn about this: excess-property checking fires on an object literal and not
|
|
24
|
+
on a variable, so a bag held in a `const` compiles clean and throws when you call the factory.
|
|
25
|
+
|
|
26
|
+
Nullish is absence everywhere, at both levels, so `{ meta: config.meta ?? null }` and
|
|
27
|
+
`{ meta: { title: config.title } }` over a config that carries neither are both fine.
|
|
28
|
+
`html({ paths })` now takes `true` or `false` rather than anything truthy, so `paths: "no"` throws
|
|
29
|
+
instead of turning path stamping on. A `fonts` mapping given as an array is refused by
|
|
30
|
+
`@quario/html` and `@quario/layout` rather than read as families named `0`, `1`, `2`.
|
|
31
|
+
|
|
32
|
+
Every host-option failure is now a `TypeError` rather than a plain `Error`. A `catch` that tests
|
|
33
|
+
`instanceof Error` is unaffected. One that compares the constructor is not.
|
|
34
|
+
|
|
35
|
+
`@quario/docx` is relaxed in three places, each of them now accepting what it refused: `meta: null`
|
|
36
|
+
and `page: null` are absence rather than errors, a property written as `meta: { title: undefined }`
|
|
37
|
+
is a property you did not write rather than one of the wrong type, and an inherited enumerable key
|
|
38
|
+
is no longer reported as an option you wrote.
|
|
39
|
+
|
|
40
|
+
`@quario/pdf`, `@quario/xlsx` and `@quario/docx` now copy `meta` at the factory call. Mutating the
|
|
41
|
+
object you passed no longer changes what a configured target writes.
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- The engine verifies a license key against a new signing key.
|
|
46
|
+
|
|
3
47
|
## 0.8.0
|
|
4
48
|
|
|
5
49
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Compiles a report and returns the compiled report. `stream(data)` is the raw eve
|
|
|
63
63
|
(e.g. `html()` from `@quario/html`) or your own.
|
|
64
64
|
|
|
65
65
|
A render is async: quario awaits key verification before the target sees its first event. A
|
|
66
|
-
malformed target throws synchronously from `render`, before the first event
|
|
66
|
+
malformed target throws synchronously from `render`, before the first event. Definition problems
|
|
67
67
|
throw earlier, at `report()`.
|
|
68
68
|
|
|
69
69
|
The data pre-pass (select, filter, sort, aggregate) runs when you call the renderer, because a
|
|
@@ -80,7 +80,7 @@ report.paths; // padvinder's deeply frozen dependency topology for the `data` qu
|
|
|
80
80
|
|
|
81
81
|
Events arrive in render order: `report-start`, report `header` items, then either the `empty`
|
|
82
82
|
items or the group/detail walk, then `footer` items, `report-end`. Group instances bracket their
|
|
83
|
-
content with `group-start`/`group-end
|
|
83
|
+
content with `group-start`/`group-end`. A table detail yields `table-start`, one `row` per visible
|
|
84
84
|
row, one `total-row` per emitted total row, and `table-end`.
|
|
85
85
|
|
|
86
86
|
| Event | Carries |
|
|
@@ -88,7 +88,7 @@ row, one `total-row` per emitted total row, and `table-end`.
|
|
|
88
88
|
| `report-start` | `params`, resolved report `aggregates`, optional `page` band closures, `columns`, `style` (the report default), `marking`, `margin`, `headerHeight`, and the instance's `locale` / `currency` / `timeZone` when set |
|
|
89
89
|
| `item` | `role`, `path`, `tokens`, optional `style`, `run` |
|
|
90
90
|
| `image` | `role`, `path`, `bytes`, `format` (`png` \| `jpeg`), `fit`, optional `alt`, `style`, `run` |
|
|
91
|
-
| `split-start` | `role`, `slots` (each an optional `width`), optional `style
|
|
91
|
+
| `split-start` | `role`, `slots` (each an optional `width`), optional `style`. One `item` or `image` per slot follows, then `split-end` |
|
|
92
92
|
| `split-end` | - |
|
|
93
93
|
| `group-start` | `name`, `depth`, `key`, `aggregates`, `path`, optional `break`, `reset`, `columns` |
|
|
94
94
|
| `group-end` | `name`, `depth` |
|
|
@@ -108,18 +108,18 @@ a literal and a value, so the cell is text only.
|
|
|
108
108
|
|
|
109
109
|
**A page-number token names itself.** A value token whose interpolation is exactly
|
|
110
110
|
`{{ page.number }}` or `{{ page.total }}` also carries `field`, holding that same string. The
|
|
111
|
-
value is the number
|
|
111
|
+
value is the number the render used for this page. `field` says which page value the token stands
|
|
112
112
|
for, so a target whose own document format numbers pages can write its own live field there
|
|
113
113
|
instead. Anything computed from them carries no `field`.
|
|
114
114
|
|
|
115
115
|
**Escaping is the consumer's job.** A target that embeds values in markup must escape them at its
|
|
116
116
|
own edge.
|
|
117
117
|
|
|
118
|
-
**`report-start.marking`** carries the evaluation wording when the render
|
|
119
|
-
verification is still settling. Licensed streams omit it. Targets place the marking
|
|
118
|
+
**`report-start.marking`** carries the evaluation wording when no license covers the render, or while
|
|
119
|
+
verification is still settling. Licensed streams omit it. Targets place the marking. They do not
|
|
120
120
|
author its wording. **`columns`** on `report-start` / `group-start` is the declared
|
|
121
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
|
|
122
|
+
present. `@quario/pdf` and `@quario/html` lay page columns out.
|
|
123
123
|
xlsx never will.
|
|
124
124
|
|
|
125
125
|
### `text(tokens)`
|
|
@@ -133,7 +133,7 @@ Exactly one value token holding a finite number, a boolean, or a valid `Date` ke
|
|
|
133
133
|
pre-stringify value. Anything else, including a lone null, reports `undefined` and joins to
|
|
134
134
|
display text. Passing the cell's resolved `format` kind opts into the seam's one coercion: under
|
|
135
135
|
`"date"`, an RFC 3339 string revives to the `Date` it names. Spreadsheet consumers use this for
|
|
136
|
-
real numeric cells
|
|
136
|
+
real numeric cells.
|
|
137
137
|
[`@quario/csv`](https://www.npmjs.com/package/@quario/csv) is the short form.
|
|
138
138
|
|
|
139
139
|
### `styledRuns(tokens)`
|
|
@@ -147,12 +147,12 @@ target cannot drift from them.
|
|
|
147
147
|
### `walk(events, handlers)` / `breathe()`
|
|
148
148
|
|
|
149
149
|
`walk` is the delivery driver every official target uses. Pass one render's event iterable and
|
|
150
|
-
per-event handlers keyed by type
|
|
151
|
-
the loop
|
|
150
|
+
per-event handlers keyed by type. A missing handler ignores that event. It pulls on demand,
|
|
151
|
+
returns the loop between batches, and delivers the opening event before pulling a second, so a
|
|
152
152
|
target can settle `report-start` (page bands, marking) there instead of draining the stream
|
|
153
153
|
itself.
|
|
154
154
|
|
|
155
|
-
`breathe()` is that
|
|
155
|
+
`breathe()` is that return alone. Await it between batches of a loop you own. `walk` already
|
|
156
156
|
calls it for you.
|
|
157
157
|
|
|
158
158
|
### Presentation helpers
|
|
@@ -163,11 +163,11 @@ cell the same way:
|
|
|
163
163
|
- `display(value)` — the scalar rule `text()` joins with: a `Date` as ISO 8601 UTC, nullish as
|
|
164
164
|
the empty string, everything else `String(value)`.
|
|
165
165
|
- `format(value, style?, options?)` — presents a token under the cell's resolved `format`
|
|
166
|
-
declaration (its kind and modifier, and for `currency` the cell's own code)
|
|
167
|
-
`display()`.
|
|
166
|
+
declaration (its kind and modifier, and for `currency` the cell's own code). It answers
|
|
167
|
+
`undefined` when the kind does not apply, so the caller falls back to `display()`.
|
|
168
168
|
- `fractionDigits(style?, options?)` — the digit count a resolved `format` declaration presents:
|
|
169
169
|
the kind's own (two for `number` and `percent`, a currency's minor units for `currency`) unless
|
|
170
|
-
the declaration's `digits` overrides it
|
|
170
|
+
the declaration's `digits` overrides it. It answers `undefined` where there is no count.
|
|
171
171
|
- `currencyOf(style?, options?)` — which code a money cell wears: its own, else the instance's.
|
|
172
172
|
- `isReportBand(role)` — whether a role names one of the report's own bands rather than a
|
|
173
173
|
group's.
|
|
@@ -176,8 +176,8 @@ cell the same way:
|
|
|
176
176
|
|
|
177
177
|
### `validate(schema, functions?)`
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
empty array means valid.
|
|
179
|
+
Validates a definition without rendering it. It returns every problem as a path-prefixed string.
|
|
180
|
+
An empty array means valid.
|
|
181
181
|
|
|
182
182
|
```js
|
|
183
183
|
validate({ data: "$.o[*]", sort: [{ by: "=@.a", dir: "up" }] });
|
|
@@ -202,7 +202,7 @@ is not a problem at a lower severity, which is why it has no `diagnostic` — no
|
|
|
202
202
|
engine decided. Neither warning below locates into an authored source, so none carries a `source`
|
|
203
203
|
today. **A warning is never fatal**: a document carrying only warnings compiles and
|
|
204
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
|
|
205
|
+
a cell whose `format` is not `"currency"`, and a table where the author sized every column and the widths
|
|
206
206
|
total under 100 — and the list is advisory and deliberately incomplete, so a quiet one is not a
|
|
207
207
|
promise that every declaration will be read. `validate()` returns problems only.
|
|
208
208
|
|
|
@@ -217,10 +217,10 @@ else console.error(problems[0].path, problems[0].message);
|
|
|
217
217
|
|
|
218
218
|
True when a caught value is a **located diagnostic**: an error xprsn, sjabloon or padvinder
|
|
219
219
|
minted — thrown by that engine, or re-thrown by quario with the engine original behind it.
|
|
220
|
-
Authentication
|
|
220
|
+
Authentication tests identity. An error that only matches the shape does not pass.
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
function's own throw
|
|
222
|
+
Location is not what the guard reads: quario's own verdicts on a document and a registered
|
|
223
|
+
function's own throw carry a location too, and neither is a diagnostic. Errors a report throws
|
|
224
224
|
name the path they failed at — and the offending source, where there is one — while keeping their
|
|
225
225
|
original type (`SyntaxError`, `TypeError`, `RangeError`). What a diagnostic adds on top is
|
|
226
226
|
metadata an engine vouches for: `code`, `start`/`end` offsets, and, for a query budget in place
|
|
@@ -246,7 +246,7 @@ try {
|
|
|
246
246
|
| `run.<name>` | Running accumulator values on the current detail row |
|
|
247
247
|
|
|
248
248
|
Each anchor is a distinct object. Absent reads are `null`, so `x == null` holds for a missing
|
|
249
|
-
field
|
|
249
|
+
field. Reading _through_ a null base still throws, so use `?.`.
|
|
250
250
|
|
|
251
251
|
## Options
|
|
252
252
|
|
|
@@ -254,15 +254,15 @@ field; reading _through_ a null base still throws, so use `?.`.
|
|
|
254
254
|
quario({ query: { maxNodes: 10_000, maxDepth: 64, maxResults: 1_000 } }).report(schema, functions);
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
`query` bounds the JSONPath data selection. Hosts set budgets through this API argument
|
|
258
|
-
|
|
257
|
+
`query` bounds the JSONPath data selection. Hosts set budgets through this API argument.
|
|
258
|
+
Definitions do not carry it. Failures point at `data`, keep their `RangeError` type, and
|
|
259
259
|
carry `code`, `limit`, and `actual`. Every render starts with fresh counters.
|
|
260
260
|
|
|
261
261
|
## Writing a render target
|
|
262
262
|
|
|
263
263
|
Read the stream through the public API. Do not reach for engine internals. A complete, tested
|
|
264
264
|
Markdown target lives in the repository at `example/markdown.js` in about 70 lines. The built-in
|
|
265
|
-
targets
|
|
265
|
+
targets use the same public API.
|
|
266
266
|
|
|
267
267
|
Two rules a target owes its users: escape or neutralize every `value` token at your own edge, and
|
|
268
268
|
map the [style vocabulary](https://getquario.com/docs/reference/style-declarations/) to your own
|
|
@@ -291,10 +291,10 @@ the [engine reference](https://getquario.com/docs/reference/quario/) is this pac
|
|
|
291
291
|
|
|
292
292
|
## License
|
|
293
293
|
|
|
294
|
-
Commercial software with readable source.
|
|
295
|
-
licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
294
|
+
Commercial software with readable source. Evaluation is free, unlimited, and watermarked.
|
|
295
|
+
Per-developer licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
296
296
|
|
|
297
|
-
Pass your license key in the options
|
|
297
|
+
Pass your license key in the options. quario verifies it offline:
|
|
298
298
|
|
|
299
299
|
```js
|
|
300
300
|
const q = quario({ license: "quario_..." });
|
package/lib/host.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a host got wrong in a target factory's options, named. A target
|
|
3
|
+
* refuses what it does not understand at the factory call, so a typo costs
|
|
4
|
+
* one stack trace at the line the host wrote rather than a wrong document
|
|
5
|
+
* nothing reports (`docs/adr/0072`).
|
|
6
|
+
*
|
|
7
|
+
* Here rather than in a target for the reason `imageError` carries beside it:
|
|
8
|
+
* every target needs it, no two of them may depend on each other, and the
|
|
9
|
+
* engine is what they all already have. Nothing an author writes reaches this
|
|
10
|
+
* module -- these are host API, and no located diagnostic is involved.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @type {(msg: string) => never} */
|
|
14
|
+
let fail = (msg) => {
|
|
15
|
+
throw TypeError(msg);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* An object a host may have written by hand: not an array, not a function, and
|
|
20
|
+
* not `null`, each of which is a different mistake wearing `typeof "object"`.
|
|
21
|
+
*
|
|
22
|
+
* @type {(value: unknown) => boolean}
|
|
23
|
+
*/
|
|
24
|
+
let plain = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
|
+
|
|
26
|
+
// Own keys only: an inherited enumerable property is the prototype's business,
|
|
27
|
+
// and naming it back at the host would name something they never wrote.
|
|
28
|
+
/** @type {(value: object, keys: readonly string[], at: string) => void} */
|
|
29
|
+
let closed = (value, keys, at) => {
|
|
30
|
+
for (let key of Object.keys(value))
|
|
31
|
+
if (!keys.includes(key)) fail(at + ': unknown option "' + key + '"');
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A target's own options, with its key set closed. Nullish is absence, which
|
|
36
|
+
* is what the engine does with every value a host omits, so a host writing
|
|
37
|
+
* `meta: config.meta ?? null` is not a host making a mistake.
|
|
38
|
+
*
|
|
39
|
+
* @template T
|
|
40
|
+
* @param {T} value The options object, or a nested one.
|
|
41
|
+
* @param {readonly string[]} keys Every key this object may carry.
|
|
42
|
+
* @param {string} at The option path, which prefixes the failure.
|
|
43
|
+
* @returns {T} `value`, unchanged.
|
|
44
|
+
*/
|
|
45
|
+
export let hostOptions = (value, keys, at) => {
|
|
46
|
+
if (value == null) return value;
|
|
47
|
+
if (!plain(value)) fail(at + ": expected an object");
|
|
48
|
+
closed(value, keys, at);
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** The three document properties a host may write. */
|
|
53
|
+
let PROPERTIES = ["title", "author", "subject"];
|
|
54
|
+
|
|
55
|
+
// Nullish is absence here too, one level down: a host writing
|
|
56
|
+
// `{ title: config.title }` over a config that carries no title has made no
|
|
57
|
+
// mistake, and a target skips the property the same way it skips a key that
|
|
58
|
+
// was never written.
|
|
59
|
+
/** @type {(value: any, at: string) => void} */
|
|
60
|
+
let everyString = (value, at) => {
|
|
61
|
+
for (let key of Object.keys(value))
|
|
62
|
+
if (value[key] != null && typeof value[key] !== "string")
|
|
63
|
+
fail(at + "." + key + ": expected a string");
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The `meta` option every document target takes, validated whole: those three
|
|
68
|
+
* properties, each a string, and never a date. What a host cannot write is
|
|
69
|
+
* what keeps a render reproducible -- each of these formats has one part with
|
|
70
|
+
* a slot for a clock, and none of them has a way to fill it.
|
|
71
|
+
*
|
|
72
|
+
* The contract is one thing, so it is checked in one place. What stays a
|
|
73
|
+
* target's own is the name each property carries in its format.
|
|
74
|
+
*
|
|
75
|
+
* Returns a **copy**, so the properties are read once, here. A host that
|
|
76
|
+
* mutates its options object between renders cannot make one configured
|
|
77
|
+
* target write two different documents, and no target has to remember to
|
|
78
|
+
* copy for itself.
|
|
79
|
+
*
|
|
80
|
+
* @param {any} value The `meta` option.
|
|
81
|
+
* @param {string} at The option path, which prefixes the failure.
|
|
82
|
+
* @returns {any} A copy of `value`, or undefined when the host wrote none.
|
|
83
|
+
*/
|
|
84
|
+
export let hostMeta = (value, at) => {
|
|
85
|
+
hostOptions(value, PROPERTIES, at);
|
|
86
|
+
if (value == null) return undefined;
|
|
87
|
+
everyString(value, at);
|
|
88
|
+
return { ...value };
|
|
89
|
+
};
|
package/lib/index.d.ts
CHANGED
|
@@ -920,6 +920,33 @@ export function isDiagnostic(e: unknown): e is QuarioDiagnostic;
|
|
|
920
920
|
*/
|
|
921
921
|
export function imageError(path: string | undefined, said: string, cause?: unknown): Error;
|
|
922
922
|
|
|
923
|
+
/** Optional document information; never includes dates, so output stays deterministic. */
|
|
924
|
+
export interface QuarioMeta {
|
|
925
|
+
title?: string;
|
|
926
|
+
author?: string;
|
|
927
|
+
subject?: string;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Close a **target factory's** option key set, for the target itself to call.
|
|
932
|
+
* A target refuses what it does not understand at the factory call, so a
|
|
933
|
+
* host's typo costs one stack trace where they wrote it rather than an option
|
|
934
|
+
* silently lost. Throws a `TypeError` prefixed with `at`.
|
|
935
|
+
*
|
|
936
|
+
* Nullish is absence. Own enumerable keys only, so an inherited property is
|
|
937
|
+
* not reported back as an option the host wrote.
|
|
938
|
+
*/
|
|
939
|
+
export function hostOptions<T>(value: T, keys: readonly string[], at: string): T;
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Validate a target's `meta` option whole: `title`, `author` and `subject`,
|
|
943
|
+
* each a string, and nothing else. Every document target takes the same three,
|
|
944
|
+
* so the contract is checked once; the name each property carries inside a
|
|
945
|
+
* given format stays that target's own. Throws a `TypeError` prefixed with
|
|
946
|
+
* `at`.
|
|
947
|
+
*/
|
|
948
|
+
export function hostMeta<T extends QuarioMeta | null | undefined>(value: T, at: string): T;
|
|
949
|
+
|
|
923
950
|
/**
|
|
924
951
|
* Join a token stream to display text: literals verbatim, values through
|
|
925
952
|
* `display()`. Re-exported from sjabloon.
|
package/lib/index.js
CHANGED
package/lib/license.js
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
// though nothing reassigns them. A key is valid for every version released inside its window
|
|
12
12
|
// (LICENSE section 7), so validity compares ISO date strings and never reads
|
|
13
13
|
// a clock — accepted output stays accepted.
|
|
14
|
-
let RELEASE = "2026-09-
|
|
14
|
+
let RELEASE = "2026-09-15"; // release-date
|
|
15
15
|
// The verifying half of the signing pair: the 65-byte uncompressed P-256
|
|
16
16
|
// point, base64url. The private half never enters the repo;
|
|
17
17
|
// scripts/license/sign.mjs mints keys against it.
|
|
18
18
|
let PUBKEY =
|
|
19
|
-
"
|
|
19
|
+
"BLT0Rp5dYp7U6qiaBO5GfEx5lOntJZAbCrbmEfE824dHhdEzfylChU7xUEDj5jnArxSjXaTu8jnBPD8MX5FbNik";
|
|
20
20
|
|
|
21
21
|
// The marking wording, stated once for the whole engine and carried to every
|
|
22
22
|
// target on `report-start` (SCHEMA.md, "License keys"): the targets own where
|