quario 0.0.1 → 0.2.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 +156 -0
- package/LICENSE +219 -0
- package/README.md +237 -1
- package/lib/index.d.ts +617 -0
- package/lib/index.js +290 -0
- package/lib/license.js +141 -0
- package/lib/locate.js +67 -0
- package/lib/names.js +19 -0
- package/lib/plan.js +1388 -0
- package/lib/scope.js +162 -0
- package/lib/stream.js +177 -0
- package/lib/style.js +81 -0
- package/package.json +55 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to quario are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2026-09-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **A report default: one `style` block for the whole document.** A top-level
|
|
15
|
+
`style` beside `header`/`detail`/`footer` states the typeface a report is set
|
|
16
|
+
in, so a document with one face names it once instead of on every item, every
|
|
17
|
+
table column, every header and every total cell. It takes `family` and
|
|
18
|
+
`size`; any other declaration there is a located definition error, exactly as
|
|
19
|
+
a text declaration on an image item is. Values are literals or `=`
|
|
20
|
+
expressions like any other style block's, resolved once per render in report
|
|
21
|
+
scope.
|
|
22
|
+
|
|
23
|
+
- **`report-start` carries the resolved report default as `style`.** A
|
|
24
|
+
report-level fact, never merged into an item's own `style`: an event's
|
|
25
|
+
`style` stays what the author wrote on that node, and a consumer composes the
|
|
26
|
+
default itself, once — under its own band-role defaults and under every
|
|
27
|
+
event's own style. Absent when the report declares none, so a consumer
|
|
28
|
+
written before this field renders in its own baseline exactly as it did.
|
|
29
|
+
|
|
30
|
+
- **An `uppercase` style declaration.** A boolean beside `bold` and `italic`,
|
|
31
|
+
literal or an `=` expression, for the capitalised column labels business
|
|
32
|
+
forms are usually set with. It is capitals, not small caps — real small caps
|
|
33
|
+
need a font feature the PDF target's built-in faces cannot supply, so the
|
|
34
|
+
declaration promises only what every target can draw. Image items keep
|
|
35
|
+
refusing it, as they refuse every text declaration.
|
|
36
|
+
|
|
37
|
+
- **A `split` item places values across the line instead of down the band.**
|
|
38
|
+
The invoice header's "seller left, customer right", which a band could not
|
|
39
|
+
say before. `{ "type": "split", "slots": [...] }` takes two or more slots,
|
|
40
|
+
each an ordinary `text` or `image` item plus an optional `width` percentage
|
|
41
|
+
under the same rules a table column's obeys; width-less slots divide what
|
|
42
|
+
the sized ones leave, evenly. A split is always the full content width and
|
|
43
|
+
never nests — it says what sits beside what, never where anything is — and
|
|
44
|
+
a slot that renders nothing keeps its width, so a line's geometry never
|
|
45
|
+
moves with the data. Splits may appear in every item array except table and
|
|
46
|
+
total cells.
|
|
47
|
+
|
|
48
|
+
- **`split-start` / `split-end` bracket a split's slots on the event stream.**
|
|
49
|
+
`split-start` carries the slot geometry, then one ordinary `item` or `image`
|
|
50
|
+
event per slot in order, then `split-end`. Existing consumers need no
|
|
51
|
+
change: the walk driver's missing-handler rule means a target that ignores
|
|
52
|
+
the bracket still receives the slot items and renders them stacked.
|
|
53
|
+
|
|
54
|
+
- **`quario().plan(schema, funcs?)` hands the whole traversal over at once.**
|
|
55
|
+
Returns `{ report, problems, anchors }` from one descent: the compiled
|
|
56
|
+
report (`null` while the document has problems), every problem as
|
|
57
|
+
`{ path, source?, message, diagnostic? }`, and a map from each compiled
|
|
58
|
+
source's schema path to the anchors and group handles it reads. A host that
|
|
59
|
+
validates and renders per edit — an editor — pays one traversal instead of
|
|
60
|
+
two, can point at a problem in its own UI without parsing a message apart,
|
|
61
|
+
and can tell where a node may safely move. `validate()` is unchanged: its
|
|
62
|
+
strings are those problems' `message` fields.
|
|
63
|
+
- **Every problem keeps its own located diagnostic.** `validate()` retained
|
|
64
|
+
`start`/`end` offsets on at most one problem per document; the structured
|
|
65
|
+
list carries each problem's own, so a consumer can underline the offending
|
|
66
|
+
character in every faulty expression rather than only the first.
|
|
67
|
+
- **Events name the definition behind them.** `item`, `image` and
|
|
68
|
+
`group-start` events carry `path`, the schema path of the node that
|
|
69
|
+
produced them; `table-start` carries `detail` and its columns their own,
|
|
70
|
+
and every table cell carries its column definition's path (a total cell its
|
|
71
|
+
`detail.total[i]` entry). Rows carry no identity — a row is data, not
|
|
72
|
+
schema. Additive: a consumer that ignores the field is unaffected.
|
|
73
|
+
- **`display(value)` joins one value the way `text()` does.** The scalar
|
|
74
|
+
display rule behind the token join, re-exported beside `text()` so a stream
|
|
75
|
+
consumer that stringifies token values itself renders exactly what the
|
|
76
|
+
official targets render — Dates included.
|
|
77
|
+
|
|
78
|
+
- **`maxDepth: Infinity` opts a query budget out.** The data query's traversal
|
|
79
|
+
budgets accept an explicit `Infinity` per key for "this budget, unbounded".
|
|
80
|
+
The 500-deep default is unchanged — it is now padvinder's own, applied for
|
|
81
|
+
every consumer rather than added by quario at the seam.
|
|
82
|
+
|
|
83
|
+
- **Located data-query errors carry padvinder's code and span.** A `data`
|
|
84
|
+
query that does not parse now surfaces with padvinder's `code` and
|
|
85
|
+
`start`/`end` offsets into the query you wrote — filter faults included —
|
|
86
|
+
alongside the band/item path quario already attached. The code names the
|
|
87
|
+
category of mistake: `PADVINDER_MISSING_ROOT`, `PADVINDER_BAD_SELECTOR`,
|
|
88
|
+
`PADVINDER_UNCLOSED_BRACKET`, `PADVINDER_BAD_STRING`,
|
|
89
|
+
`PADVINDER_UNKNOWN_FUNCTION`, or `PADVINDER_SYNTAX` for a path character or
|
|
90
|
+
filter body that is open-endedly not a query. Traversal budgets exceeded at
|
|
91
|
+
render time keep `limit`/`actual` and carry no span.
|
|
92
|
+
|
|
93
|
+
### Changed
|
|
94
|
+
|
|
95
|
+
- **Cells render straight over the engine scope chain.** A text cell no longer
|
|
96
|
+
allocates a wrapper scope and an anchor pair per cell per row — quario's
|
|
97
|
+
chain already binds `$` at the render base and `@` on the detail row, and
|
|
98
|
+
sjabloon now renders over it as-is. A 4-column stream over a million rows
|
|
99
|
+
went from 1.7s to 0.5s. No report changes what it renders. Requires
|
|
100
|
+
sjabloon 0.11.
|
|
101
|
+
|
|
102
|
+
- **A compiled report's `functions` carry signatures.** Each entry is now
|
|
103
|
+
`{ name, arity, doc? }` instead of a bare name — `arity` from the function's
|
|
104
|
+
declared parameter count (or its own numeric `arity` where rest parameters
|
|
105
|
+
mislead `length`), `doc` from an own `doc` string when it carries one — in
|
|
106
|
+
the same call-first-seen order. `names` is unchanged. Requires xprsn 0.11
|
|
107
|
+
and sjabloon 0.11.
|
|
108
|
+
|
|
109
|
+
- **A bare `Date` renders as ISO 8601 UTC, the same on every machine.**
|
|
110
|
+
Display text for a `Date` value was `String(date)`, which bakes the
|
|
111
|
+
rendering host's timezone and locale into the output — so one report
|
|
112
|
+
produced different bytes on different machines. Every target now renders a
|
|
113
|
+
valid `Date` as its `toISOString()` form through the one shared display
|
|
114
|
+
rule; an invalid `Date` keeps its deterministic `Invalid Date` text.
|
|
115
|
+
Reports that want a formatted date keep using a registered function,
|
|
116
|
+
exactly as before.
|
|
117
|
+
|
|
118
|
+
- **A bad literal pattern in the data query is a definition error.** A typo'd
|
|
119
|
+
I-Regexp written as a string literal in `match()`/`search()` used to
|
|
120
|
+
produce a plausible empty report with no signal; `report()` and
|
|
121
|
+
`validate()` now surface it at compile time as a located error, with
|
|
122
|
+
offsets at the pattern literal in the query. The class and `code` are
|
|
123
|
+
treffer's — a malformed pattern is a `SyntaxError` with `TREFFER_SYNTAX`,
|
|
124
|
+
one past treffer's limits a `RangeError` with a `TREFFER_MAX_*` code and
|
|
125
|
+
`limit`/`actual` — on the same rule that already gives xprsn's codes to a
|
|
126
|
+
fault in an expression: the engine that decided the fault names it. A
|
|
127
|
+
pattern that arrives from render data keeps RFC 9535 semantics and still
|
|
128
|
+
matches nothing at render time. Requires padvinder 0.8.
|
|
129
|
+
|
|
130
|
+
- **Each engine relocates its own diagnostic.** A located error is now a copy
|
|
131
|
+
made by the engine that raised it (xprsn, sjabloon, or padvinder), so it
|
|
132
|
+
carries every field that engine puts on a diagnostic — nothing is lost in
|
|
133
|
+
quario's re-throw, today or when an engine adds a field — and it still
|
|
134
|
+
passes that engine's own `isDiagnostic`. Host errors are wrapped as plain
|
|
135
|
+
errors with no diagnostic metadata, exactly as before. Requires xprsn 0.10,
|
|
136
|
+
sjabloon 0.9, and padvinder 0.5. No report changes what it renders.
|
|
137
|
+
|
|
138
|
+
### Fixed
|
|
139
|
+
|
|
140
|
+
- **A host error cannot pose as the report's diagnostic.** `report()` rethrows
|
|
141
|
+
the first definition problem's located engine error; it chose that error by
|
|
142
|
+
probing for a `code` property, so a host error class that stamps `code` on
|
|
143
|
+
itself in its constructor was rethrown as if the engines had raised it. The
|
|
144
|
+
choice now goes through the same identity-based authentication as
|
|
145
|
+
`isDiagnostic`; everything else falls back to a plain `SyntaxError` naming
|
|
146
|
+
the first problem, as before.
|
|
147
|
+
|
|
148
|
+
## [0.1.0] - 2026-08-27
|
|
149
|
+
|
|
150
|
+
### Added
|
|
151
|
+
|
|
152
|
+
- **A compile-once report engine.** You hand it a JSON report definition and
|
|
153
|
+
data; it plans once and streams render events that every target consumes.
|
|
154
|
+
Text cells go through sjabloon, everything else through xprsn, rows through
|
|
155
|
+
padvinder. Unlicensed output carries a marking on the opening event; each
|
|
156
|
+
target only decides where that wording goes.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Quario License Agreement
|
|
2
|
+
|
|
3
|
+
> **In short:** Quario is commercial software with visible source.
|
|
4
|
+
> Evaluate it free for as long as you like. Evaluation output is
|
|
5
|
+
> watermarked. Anything else needs a per-developer license, after
|
|
6
|
+
> which deployment is unlimited and royalty-free. This summary is not
|
|
7
|
+
> the license; the full terms below govern.
|
|
8
|
+
|
|
9
|
+
Version 1.0, August 2026
|
|
10
|
+
|
|
11
|
+
This agreement is between you and Webstronauts, trading as Quario,
|
|
12
|
+
established in the Netherlands ("we", "us"), and governs the Software
|
|
13
|
+
defined below.
|
|
14
|
+
|
|
15
|
+
Quario is commercial software. Its packages ship with readable source
|
|
16
|
+
code, but it is not open source. No rights are granted beyond those in
|
|
17
|
+
this agreement.
|
|
18
|
+
|
|
19
|
+
## 1. Definitions
|
|
20
|
+
|
|
21
|
+
- The **Software** is any software we publish under the Quario name,
|
|
22
|
+
including the `quario` and `@quario/*` packages, except where a
|
|
23
|
+
product states its own terms. Each release states the edition of this
|
|
24
|
+
agreement that governs it.
|
|
25
|
+
- **You** means the individual exercising rights under this agreement
|
|
26
|
+
and, where that individual acts for a company or other organization,
|
|
27
|
+
that organization. In that case the organization is the licensee, and
|
|
28
|
+
the rights and limits in this agreement apply to it as a whole.
|
|
29
|
+
- An **Application** is software you create that incorporates the
|
|
30
|
+
Software and adds substantial functionality beyond it. A
|
|
31
|
+
licensee-operated service that embeds the Software and renders
|
|
32
|
+
reports for your End Users is an Application under this agreement,
|
|
33
|
+
even if it does little more than render.
|
|
34
|
+
- A **Developer** is any individual (employee or contractor working on
|
|
35
|
+
your behalf) who writes or maintains code that uses the Software's
|
|
36
|
+
APIs, or who creates or maintains report definitions for use with the
|
|
37
|
+
Software. That includes people who never install or run the Software
|
|
38
|
+
themselves, when their definitions are rendered by a service that
|
|
39
|
+
embeds it.
|
|
40
|
+
- A **Seat** is a paid license for one named Developer, purchased from
|
|
41
|
+
us or an authorized reseller.
|
|
42
|
+
- **End Users** are people who interact with the Software only through
|
|
43
|
+
an Application: running it, viewing the reports it produces, or
|
|
44
|
+
supplying parameters or other input to existing report definitions
|
|
45
|
+
the Application provides. Creating or maintaining report definitions
|
|
46
|
+
makes someone a Developer. End Users do not purchase a license; their
|
|
47
|
+
rights come from section 5.
|
|
48
|
+
|
|
49
|
+
## 2. Acceptance
|
|
50
|
+
|
|
51
|
+
This file is our standing offer. Anyone who exercises the evaluation
|
|
52
|
+
rights in section 3 accepts them, subject to their conditions. All
|
|
53
|
+
other rights arise when Seats are purchased. Where a purchase is made
|
|
54
|
+
for an organization, the person completing it confirms that they are
|
|
55
|
+
authorized to accept this agreement on the organization's behalf, and
|
|
56
|
+
acceptance covers the whole organization. If you do not accept this
|
|
57
|
+
agreement, do not use the Software.
|
|
58
|
+
|
|
59
|
+
## 3. Evaluation
|
|
60
|
+
|
|
61
|
+
You may download, install, and use the Software free of charge to
|
|
62
|
+
evaluate it: assessing whether it suits your purposes, and building
|
|
63
|
+
prototypes or proofs of concept. Evaluation covers the full feature
|
|
64
|
+
set, needs no key, and has no fixed time limit. The Software marks
|
|
65
|
+
evaluation output as described in section 6.
|
|
66
|
+
|
|
67
|
+
Evaluation is limited to assessment: you may not distribute any
|
|
68
|
+
Application containing the Software, use the Software in live business
|
|
69
|
+
operations, or expose it in services offered to third parties. The
|
|
70
|
+
moment your use goes beyond evaluating the Software, it requires Seats
|
|
71
|
+
under section 4.
|
|
72
|
+
|
|
73
|
+
## 4. Developer licenses
|
|
74
|
+
|
|
75
|
+
Any use of the Software beyond section 3 requires a Seat for every
|
|
76
|
+
Developer, whether or not an Application has shipped. With those Seats
|
|
77
|
+
in place:
|
|
78
|
+
|
|
79
|
+
- Each licensed Developer may install and use the Software on any number
|
|
80
|
+
of machines for developing Applications.
|
|
81
|
+
- Automated build, test, and CI systems do not consume Seats.
|
|
82
|
+
- Seats are per named individual. They may be reassigned when a
|
|
83
|
+
Developer stops working with the Software, but not shared or pooled
|
|
84
|
+
between concurrently active Developers. Contractors' work for you is
|
|
85
|
+
covered by your Seats; work they do for other clients is not.
|
|
86
|
+
|
|
87
|
+
## 5. Distribution and runtime
|
|
88
|
+
|
|
89
|
+
With Seats under section 4 in place, you may deploy and operate your
|
|
90
|
+
Applications yourself, and distribute the Software as an embedded part
|
|
91
|
+
of them, **royalty-free**: no additional fees or per-deployment
|
|
92
|
+
licenses are required for any number of servers, copies, or End Users.
|
|
93
|
+
You may embed your license key in an Application and ship it at no
|
|
94
|
+
charge, so section 6 does not mark that Application's output.
|
|
95
|
+
|
|
96
|
+
For that purpose, we grant you the right to pass through to each End
|
|
97
|
+
User, and to anyone who distributes or operates your Application on your
|
|
98
|
+
behalf, a limited, non-exclusive, non-transferable right to execute the
|
|
99
|
+
Software only as embedded in the Application. You must not permit them
|
|
100
|
+
to extract the Software from the Application or use it to develop
|
|
101
|
+
software of their own.
|
|
102
|
+
|
|
103
|
+
If you operate a service that embeds the Software and renders reports
|
|
104
|
+
for your End Users, that service is an Application under section 1. You
|
|
105
|
+
may run it royalty-free under this section, and you may put your license
|
|
106
|
+
key in it as in any Application. Calling applications that only request
|
|
107
|
+
renders from it need not incorporate the Software. People who use those
|
|
108
|
+
applications only as End Users under section 1 get the pass-through
|
|
109
|
+
rights above.
|
|
110
|
+
|
|
111
|
+
You may not:
|
|
112
|
+
|
|
113
|
+
- distribute the Software on its own, or as part of a product whose
|
|
114
|
+
primary purpose is to provide report-engine, report-design, or
|
|
115
|
+
document-generation capability to third-party developers;
|
|
116
|
+
- expose the Software's APIs to third parties as a development tool or
|
|
117
|
+
service that substitutes for those parties buying their own Seats. A
|
|
118
|
+
licensee-operated service that embeds the Software and renders for
|
|
119
|
+
your own End Users falls outside that ban;
|
|
120
|
+
- fork, mirror, repackage, rename, or wrap the Software, or republish
|
|
121
|
+
it on any package registry or code-hosting platform, other than as
|
|
122
|
+
embedded in an Application.
|
|
123
|
+
|
|
124
|
+
For as long as this agreement is in force, you may not create any tool,
|
|
125
|
+
library, product, or service that competes with the Software or any
|
|
126
|
+
other Quario product, whether the competition is direct or indirect;
|
|
127
|
+
this restriction does not survive termination. You may not remove or
|
|
128
|
+
alter copyright or license notices in the Software. If your Application
|
|
129
|
+
is itself a closed development product for third-party developers (OEM
|
|
130
|
+
redistribution), contact us for separate terms.
|
|
131
|
+
|
|
132
|
+
## 6. License keys and output marking
|
|
133
|
+
|
|
134
|
+
The Software accepts a license key that evidences your Seats under
|
|
135
|
+
section 4. Key checks run offline.
|
|
136
|
+
|
|
137
|
+
Without a valid key, or with a key whose validity period does not cover
|
|
138
|
+
the installed version's release date, the Software runs with its full
|
|
139
|
+
feature set but marks its output as unlicensed. Using a key you are not
|
|
140
|
+
entitled to is a material breach of this agreement.
|
|
141
|
+
|
|
142
|
+
You may not remove, disable, or circumvent the key check or the output
|
|
143
|
+
marking, or distribute anything that does.
|
|
144
|
+
|
|
145
|
+
## 7. Term, updates, and perpetual fallback
|
|
146
|
+
|
|
147
|
+
Seats are sold as an annual subscription. While the subscription is
|
|
148
|
+
active, licensed Developers receive all Software versions we release,
|
|
149
|
+
and support for the Software through our issue tracker and by email. We
|
|
150
|
+
provide support with commercially reasonable effort; we do not guarantee
|
|
151
|
+
specific response times.
|
|
152
|
+
|
|
153
|
+
If the subscription lapses, your license to the versions released during
|
|
154
|
+
your subscription is **perpetual, for the number of Seats held at the
|
|
155
|
+
lapse**: up to that many Developers may keep using those versions, and
|
|
156
|
+
your Applications may keep shipping them, forever, at no further cost.
|
|
157
|
+
Versions released after the lapse require renewal. We encode this rule
|
|
158
|
+
in purchased keys: a key remains valid for every version released
|
|
159
|
+
during its validity period, with no end date.
|
|
160
|
+
|
|
161
|
+
## 8. Source availability
|
|
162
|
+
|
|
163
|
+
The Software's packages ship with readable source code so that you can
|
|
164
|
+
review and debug what you run. Readable source does not grant extra
|
|
165
|
+
rights: this agreement governs all use of the source, and you may
|
|
166
|
+
publish the Software (modified or not) only as part of an Application
|
|
167
|
+
under section 5.
|
|
168
|
+
|
|
169
|
+
## 9. Ownership, trademarks, and feedback
|
|
170
|
+
|
|
171
|
+
We license the Software; we retain all intellectual property rights in
|
|
172
|
+
it. Report definitions, data, and output you create with the Software
|
|
173
|
+
are yours. "Quario" and the Quario logo are our trademarks; this
|
|
174
|
+
agreement grants no rights to use them beyond identifying the Software.
|
|
175
|
+
If you send us feedback or suggestions, we may use them without
|
|
176
|
+
obligation.
|
|
177
|
+
|
|
178
|
+
## 10. Warranty and liability
|
|
179
|
+
|
|
180
|
+
We provide the Software as is. To the fullest extent the law allows, we
|
|
181
|
+
disclaim all warranties and conditions, express or implied, including
|
|
182
|
+
merchantability, fitness for a particular purpose, and non-infringement.
|
|
183
|
+
|
|
184
|
+
To the fullest extent the law allows: we are not liable for indirect or
|
|
185
|
+
consequential damages, loss of profits, loss of data, or business
|
|
186
|
+
interruption; and our total aggregate liability under this agreement is
|
|
187
|
+
limited to the greater of EUR 100 and the fees you paid us in the
|
|
188
|
+
twelve months preceding the event giving rise to the claim. These
|
|
189
|
+
limitations do not apply where the damage results from the intent or
|
|
190
|
+
deliberate recklessness (opzet of bewuste roekeloosheid) of us or our
|
|
191
|
+
management, or where mandatory law does not permit them.
|
|
192
|
+
|
|
193
|
+
## 11. Termination and survival
|
|
194
|
+
|
|
195
|
+
If you materially breach this agreement, we may terminate it by notice.
|
|
196
|
+
If the breach can be remedied, we will first allow 30 days from that
|
|
197
|
+
notice to remedy it, and termination takes effect only if the breach
|
|
198
|
+
remains. On termination, the licenses granted here end.
|
|
199
|
+
Perpetual-fallback rights under section 7 survive termination only if
|
|
200
|
+
they arose from a lapse that predates the breach, and section 5
|
|
201
|
+
survives with them to the extent needed to keep exercising them.
|
|
202
|
+
Sections 8 through 12 survive any termination.
|
|
203
|
+
|
|
204
|
+
## 12. General
|
|
205
|
+
|
|
206
|
+
Dutch law governs this agreement. Disputes go to the competent court for
|
|
207
|
+
our registered office. You may not assign this agreement without our
|
|
208
|
+
consent, except to a successor of your whole business; we may assign it
|
|
209
|
+
to a successor of ours. If a provision is unenforceable, the remainder
|
|
210
|
+
stays in force. Failure to enforce a provision does not waive it. This
|
|
211
|
+
agreement, together with your order for Seats, is the entire agreement
|
|
212
|
+
about the Software, unless we agree otherwise in writing.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
**Purchasing and contact:** <contact@getquario.com> ·
|
|
217
|
+
<https://getquario.com>
|
|
218
|
+
|
|
219
|
+
Copyright © 2026 Webstronauts. All rights reserved.
|
package/README.md
CHANGED
|
@@ -1 +1,237 @@
|
|
|
1
|
-
quario
|
|
1
|
+
# quario
|
|
2
|
+
|
|
3
|
+
**The report engine.** Turns a JSON report definition plus data into a stream of structured render
|
|
4
|
+
events. Every render target ([`@quario/html`](https://www.npmjs.com/package/@quario/html),
|
|
5
|
+
[`@quario/pdf`](https://www.npmjs.com/package/@quario/pdf),
|
|
6
|
+
[`@quario/xlsx`](https://www.npmjs.com/package/@quario/xlsx),
|
|
7
|
+
[`@quario/csv`](https://www.npmjs.com/package/@quario/csv), or your own) consumes that
|
|
8
|
+
stream.
|
|
9
|
+
|
|
10
|
+
The engine does not escape text, emit HTML, or format pages or cells. Install a target when you
|
|
11
|
+
want output. Use this package when you want to _build_ a target, or to consume report structure
|
|
12
|
+
as data.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install quario
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
ESM-only, Node 22+. Dependencies: [xprsn](https://github.com/getquario/xprsn) (expressions),
|
|
21
|
+
[sjabloon](https://github.com/getquario/sjabloon) (templates),
|
|
22
|
+
[padvinder](https://github.com/getquario/padvinder) (JSONPath).
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import { quario, text } from "quario";
|
|
28
|
+
|
|
29
|
+
const schema = {
|
|
30
|
+
data: "$.orders[*]",
|
|
31
|
+
aggregates: { total: "sum:=@.price * @.qty" },
|
|
32
|
+
detail: [{ type: "text", value: "{{ @.product }} — {{ @.price * @.qty }}" }],
|
|
33
|
+
footer: [{ type: "text", value: "Total {{ $.total }}" }],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const report = quario().report(schema);
|
|
37
|
+
|
|
38
|
+
for (const e of report.stream({ orders: [{ product: "Desk", price: 250, qty: 2 }] })) {
|
|
39
|
+
if (e.type === "item") console.log(e.role, text(e.tokens));
|
|
40
|
+
}
|
|
41
|
+
// detail Desk — 500
|
|
42
|
+
// report-footer Total 500
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`report()` compiles the definition **once** into a tree of closures. You render the compiled
|
|
46
|
+
report per dataset with `report.render(target, data)`, passing the target and its configuration
|
|
47
|
+
at the call, so one compile serves any number of target configurations. Definition errors
|
|
48
|
+
surface at compile time, so compile at startup and render in your request path.
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
### `quario(options?)`
|
|
53
|
+
|
|
54
|
+
Creates a configured instance with host-level controls: `{ query?, license? }`. Returns
|
|
55
|
+
`{ report, license }`. `license` settles with this instance's key verification as
|
|
56
|
+
`{ licensed, licensee?, id? }`.
|
|
57
|
+
|
|
58
|
+
### `report(schema, functions?)`
|
|
59
|
+
|
|
60
|
+
Compiles a report and returns the compiled report. `stream(data)` is the raw event generator.
|
|
61
|
+
`render(target, data)` takes a self-naming `{ name, compile }` object from a target factory
|
|
62
|
+
(e.g. `html()` from `@quario/html`) or your own.
|
|
63
|
+
|
|
64
|
+
A render is async: quario awaits key verification before the target sees its first event. A
|
|
65
|
+
malformed target throws at compile time, like any other definition error.
|
|
66
|
+
|
|
67
|
+
The data pre-pass (select, filter, sort, aggregate) runs when you call the renderer, because a
|
|
68
|
+
report header may interpolate a report aggregate. Event emission pulls on demand, so a consumer
|
|
69
|
+
that stops early does not pay for the rest of the walk.
|
|
70
|
+
|
|
71
|
+
The compiled report carries metadata:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
report.names; // free variable names the expressions read, excluding engine anchors
|
|
75
|
+
report.functions; // registry function names the definition calls
|
|
76
|
+
report.paths; // padvinder's deeply frozen dependency topology for the `data` query
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Events arrive in render order: `report-start`, report `header` items, then either the `empty`
|
|
80
|
+
items or the group/detail walk, then `footer` items, `report-end`. Group instances bracket their
|
|
81
|
+
content with `group-start`/`group-end`; a table detail yields `table-start`, one `row` per visible
|
|
82
|
+
row, an optional `total-row`, and `table-end`.
|
|
83
|
+
|
|
84
|
+
| Event | Carries |
|
|
85
|
+
| -------------- | ------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `report-start` | `params`, resolved report `aggregates`, optional `page` band closures, `columns`, `marking` |
|
|
87
|
+
| `item` | `role`, `path`, `tokens`, optional `style`, `run` |
|
|
88
|
+
| `image` | `role`, `path`, `bytes`, `format` (`png` \| `jpeg`), `fit`, optional `alt`, `style`, `run` |
|
|
89
|
+
| `group-start` | `name`, `depth`, `key`, `aggregates`, `path`, optional `break`, `reset`, `columns` |
|
|
90
|
+
| `group-end` | `name`, `depth` |
|
|
91
|
+
| `table-start` | `path`, `columns` (each `header`, `path`, optional `width`) |
|
|
92
|
+
| `row` | `cells`, optional `style`, `run` |
|
|
93
|
+
| `total-row` | `cells` |
|
|
94
|
+
| `table-end` | - |
|
|
95
|
+
| `report-end` | - |
|
|
96
|
+
|
|
97
|
+
**Cells carry tokens.** A cell is `{ tokens, style? }`. Each token is either `{ literal }`
|
|
98
|
+
(static template text, verbatim) or `{ value }` (one interpolation's _pre-format_ value, the
|
|
99
|
+
expression result before any stringification). This is the typed seam: a cell whose template is `{{ @.amount }}` holds one value token with the
|
|
100
|
+
number itself, so a spreadsheet consumer writes a real numeric cell. `Total: {{ @.amount }}` mixes
|
|
101
|
+
a literal and a value, so the cell is text only.
|
|
102
|
+
|
|
103
|
+
**Escaping is the consumer's job.** A target that embeds values in markup must escape them at its
|
|
104
|
+
own edge.
|
|
105
|
+
|
|
106
|
+
**`report-start.marking`** carries the evaluation wording when the render is unlicensed, or while
|
|
107
|
+
verification is still settling. Licensed streams omit it. Targets place the marking; they do not
|
|
108
|
+
author its wording. **`columns`** on `report-start` / `group-start` is the declared
|
|
109
|
+
page column count when present. No shipped target honors it yet;
|
|
110
|
+
xlsx never will.
|
|
111
|
+
|
|
112
|
+
### `text(tokens)`
|
|
113
|
+
|
|
114
|
+
Joins a token stream to display text: literals verbatim, values as `String(value ?? "")`, run
|
|
115
|
+
styles ignored. Re-exported from sjabloon so consumers do not hand-roll the join.
|
|
116
|
+
|
|
117
|
+
### `typed(tokens)`
|
|
118
|
+
|
|
119
|
+
Exactly one value token holding a finite number, a boolean, or a valid `Date` keeps that
|
|
120
|
+
pre-stringify value. Anything else, including a lone null, reports `undefined` and joins to
|
|
121
|
+
display text. Spreadsheet consumers use this for real numeric cells;
|
|
122
|
+
[`@quario/csv`](https://www.npmjs.com/package/@quario/csv) is the short form.
|
|
123
|
+
|
|
124
|
+
### `walk(events, handlers)` / `breathe()`
|
|
125
|
+
|
|
126
|
+
`walk` is the delivery driver every official target uses. Pass one render's event iterable and
|
|
127
|
+
per-event handlers keyed by type; a missing handler ignores that event. It pulls on demand, hands
|
|
128
|
+
the loop back between batches, and delivers the opening event before pulling a second, so a
|
|
129
|
+
target can settle `report-start` (page bands, marking) there instead of draining the stream
|
|
130
|
+
itself.
|
|
131
|
+
|
|
132
|
+
`breathe()` is that hand-back alone. Await it between batches of a loop you own; `walk` already
|
|
133
|
+
calls it for you.
|
|
134
|
+
|
|
135
|
+
### `validate(schema, functions?)`
|
|
136
|
+
|
|
137
|
+
Checks a definition without rendering it. Returns every problem as a path-prefixed string; an
|
|
138
|
+
empty array means valid.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
validate({ data: "$.o[*]", sort: [{ by: "=@.a", dir: "up" }] });
|
|
142
|
+
// ['sort[0].dir: unknown sort direction "up"']
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`report()` throws on the first problem instead. Validation and compilation share one traversal,
|
|
146
|
+
so `validate()` can never disagree with what `report()` accepts.
|
|
147
|
+
|
|
148
|
+
### `quario().plan(schema, functions?)`
|
|
149
|
+
|
|
150
|
+
The one traversal, whole — for hosts that validate and render in a loop, like an editor. Returns
|
|
151
|
+
`{ report, problems, anchors }`: the compiled report (`null` while the document has problems),
|
|
152
|
+
every problem structurally as `{ path, source?, message, diagnostic? }` (the `message` is exactly
|
|
153
|
+
`validate()`'s string, and every problem keeps its own located diagnostic with `start`/`end`
|
|
154
|
+
offsets, not only the first), and `anchors`, mapping each compiled source's schema path to the
|
|
155
|
+
anchors and group handles it reads — the unfiltered complement of `names`.
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
const { report, problems, anchors } = quario().plan(schema);
|
|
159
|
+
if (report) await report.render(html(), data);
|
|
160
|
+
else console.error(problems[0].path, problems[0].message);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### `isDiagnostic(error)`
|
|
164
|
+
|
|
165
|
+
True when a caught value is one of the stack's located errors: quario's own, or one thrown
|
|
166
|
+
directly by xprsn, sjabloon, or padvinder. Authentication checks identity. An error that only
|
|
167
|
+
matches the shape does not pass.
|
|
168
|
+
|
|
169
|
+
Located errors name their band path and offending source while keeping their original type
|
|
170
|
+
(`SyntaxError`, `TypeError`, `RangeError`). They carry `code`, `start`/`end` offsets, and (for
|
|
171
|
+
query budget failures) `limit` and `actual`.
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
try {
|
|
175
|
+
await renderer(data);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
if (isDiagnostic(e)) console.error("report problem:", e.message);
|
|
178
|
+
else throw e; // one of your own functions failed
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## The scope model
|
|
183
|
+
|
|
184
|
+
| Anchor | Is |
|
|
185
|
+
| ------------- | ------------------------------------------------------------------- |
|
|
186
|
+
| `@` | The current row. Unbound outside detail rows, so `@.x` throws there |
|
|
187
|
+
| `$` | The report root: `$.input`, `$.params`, and report aggregates |
|
|
188
|
+
| `<groupName>` | A named handle per enclosing group: `.key` plus its aggregates |
|
|
189
|
+
| `run.<name>` | Running accumulator values on the current detail row |
|
|
190
|
+
|
|
191
|
+
Each anchor is a distinct object. Absent reads are `null`, so `x == null` holds for a missing
|
|
192
|
+
field; reading _through_ a null base still throws, so use `?.`.
|
|
193
|
+
|
|
194
|
+
## Options
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
quario({ query: { maxNodes: 10_000, maxDepth: 64, maxResults: 1_000 } }).report(schema, functions);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`query` bounds the JSONPath data selection. Hosts set budgets through this API argument;
|
|
201
|
+
definitions do not carry it. Failures are located at `data`, keep their `RangeError` type, and
|
|
202
|
+
carry `code`, `limit`, and `actual`. Every render starts with fresh counters.
|
|
203
|
+
|
|
204
|
+
## Writing a render target
|
|
205
|
+
|
|
206
|
+
Read the stream through the public API. Do not reach for engine internals. A complete, tested
|
|
207
|
+
Markdown target lives in the repository at `example/markdown.js` in about 70 lines. The built-in
|
|
208
|
+
targets are written against the same public API.
|
|
209
|
+
|
|
210
|
+
Two rules a target owes its users: escape or neutralize every `value` token at your own edge, and
|
|
211
|
+
map the style vocabulary to your own formatting model rather than expecting CSS.
|
|
212
|
+
|
|
213
|
+
The stream is additive. The walk driver skips any event you register no handler for, so a target
|
|
214
|
+
that ignores a newer event (for example, `example/markdown.js` has none for `image`) keeps rendering
|
|
215
|
+
when a schema uses one.
|
|
216
|
+
|
|
217
|
+
A target that runs a loop of its own can `await breathe()` between batches to hand the event loop
|
|
218
|
+
back, as `walk` does for the targets that drive through it.
|
|
219
|
+
|
|
220
|
+
## Content Security Policy
|
|
221
|
+
|
|
222
|
+
Expressions and templates compile to closures. There is no string-to-code path anywhere in this
|
|
223
|
+
package, so it runs under a script policy that omits `unsafe-eval`. The test suite enforces it
|
|
224
|
+
under Node's `--disallow-code-generation-from-strings` flag, a source scan, and a Playwright
|
|
225
|
+
harness that loads the published files under a strict CSP.
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
|
|
230
|
+
licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
231
|
+
|
|
232
|
+
Pass your license key in the options; it is verified offline:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
const q = quario({ license: "quario_..." });
|
|
236
|
+
await q.license; // { licensed: true, licensee: "Acme BV", id: "1-ACME" }
|
|
237
|
+
```
|