quario 0.13.0 → 0.14.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 +25 -0
- package/LICENSE +176 -154
- package/lib/format.js +1 -1
- package/lib/host.js +84 -5
- package/lib/image.js +105 -9
- package/lib/index.d.ts +37 -5
- package/lib/index.js +12 -26
- package/lib/license.js +14 -3
- package/lib/plan.js +53 -11
- package/lib/schema.json +21 -1
- package/lib/stream.js +2 -2
- package/lib/style.js +3 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# quario
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **`quario(options)` now refuses an option it does not understand, like every target factory.** Before, the engine read the keys it knew and ignored the rest. A typo such as `quario({ licence })` therefore marked every render as unlicensed, and nothing said why. Now an unknown key, a value of the wrong type, or an `options` that is not an object throws a `TypeError` at the call. The message names the option path, such as `options.license`.
|
|
8
|
+
|
|
9
|
+
**`locale`, `timeZone` and `currency` are now checked at the call.** Before, a value that `Intl` refused did not fail. Every formatted cell showed its raw value instead. Now `quario({ locale: "en_US" })` and `quario({ timeZone: "Europe/Amsterdm" })` throw a `TypeError`. `currency` must be three upper-case letters, the same rule that a cell's own `currency` declaration follows. A cell's own `currency` still fails soft.
|
|
10
|
+
|
|
11
|
+
**The `query` budgets are now checked at the call.** Before, the engine checked them only when a report compiled its first query. A report with no query never reported a bad budget. The error still comes from padvinder, in its own words.
|
|
12
|
+
|
|
13
|
+
**`plan(schema, functions, options)` now refuses an unknown key in `options`.** Before, `{ target: [...] }` checked nothing and said nothing. A `targets` that is not an array also throws a `TypeError`.
|
|
14
|
+
|
|
15
|
+
**A bad `schemes` allowlist now throws a `TypeError`, not an `Error`.** The message starts with `options.schemes:`, not `schemes:`. A `null` allowlist still throws.
|
|
16
|
+
|
|
17
|
+
**What this changes for you.** An options object that carries keys for another consumer stops working when you pass it to `quario()`. Pass `quario()` only the six keys it takes: `query`, `license`, `locale`, `currency`, `timeZone` and `schemes`.
|
|
18
|
+
|
|
19
|
+
- **A group can now order and cut its instances.** Declare `instances` beside `sort`, with an optional `sort` and an optional `take` inside it. They order and cut the group's instances, as the group's own `sort` and `take` order and cut the rows inside each instance. Each `instances.sort` key reads the group's own handle once per instance, so `instances: { sort: [{ by: "=region.subtotal", dir: "desc" }], take: 5 }` keeps the five regions with the largest subtotals, largest first. `@` and `run` are unbound there. A dropped instance never renders, and no running value steps over it. The totals around the group still count it, as they count a row that a group's `take` drops. A group without `instances` renders every instance in first-seen order, so existing reports render as before.
|
|
20
|
+
- **An image item now accepts SVG bytes.** Before, `source` had to yield PNG or JPEG bytes, and SVG bytes were a render error. Now the engine also names UTF-8 text whose root element is `<svg` as an SVG, and the image event carries `format: "svg"`. The engine reads the size from the root's `width` and `height` when both are absolute lengths, and else from its `viewBox`. An SVG that states no size this way is the same render error as a PNG that is too short to state its size. The message for bytes that are not an image now says "PNG, JPEG or SVG".
|
|
21
|
+
|
|
22
|
+
The HTML target puts an SVG in an `<img>` under `data:image/svg+xml`, so the SVG never becomes markup in the fragment. The PDF target draws an SVG as vector paths. It draws no picture that the SVG names, and it fetches nothing. SVG `<text>` draws in the standard Helvetica. The viewer paints an SVG through a `data:` URI, so a page that shows one needs the Content Security Policy grant `img-src data:`. A PNG or a JPEG still needs no grant there. The XLSX and DOCX targets cannot draw an SVG, so an SVG image is a render error that names the item.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- **The package now carries edition 1.1 of the Quario License Agreement.** A license now covers one production deployment, not one developer. Each deployment needs one license key. Development, staging, CI and test environments need no key. A key covers every release dated up to one year after you buy it, and those releases stay licensed for good. A key is not a subscription, and it never renews automatically. A refunded or reversed payment voids the keys it bought. Software that your customers install or resell needs separate OEM terms.
|
|
27
|
+
|
|
3
28
|
## 0.13.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
package/LICENSE
CHANGED
|
@@ -1,179 +1,200 @@
|
|
|
1
1
|
# Quario License Agreement
|
|
2
2
|
|
|
3
3
|
> **In short:** Quario is commercial software with visible source.
|
|
4
|
-
>
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
>
|
|
4
|
+
> You may evaluate it and develop with it free, for as long as you
|
|
5
|
+
> like. The Software marks output that no license key covers. Each
|
|
6
|
+
> production deployment needs one license key. A key covers one year
|
|
7
|
+
> of releases, and those releases stay licensed for good. This summary
|
|
8
|
+
> is not the license. The full terms below govern.
|
|
8
9
|
|
|
9
|
-
Version 1.
|
|
10
|
+
Version 1.1, October 2026
|
|
10
11
|
|
|
11
12
|
This agreement is between you and Webstronauts, trading as Quario,
|
|
12
|
-
established in the Netherlands ("we", "us")
|
|
13
|
+
established in the Netherlands ("we", "us"). It governs the Software
|
|
13
14
|
defined below.
|
|
14
15
|
|
|
15
16
|
Quario is commercial software. Its packages ship with readable source
|
|
16
|
-
code, but it is not open source.
|
|
17
|
-
|
|
17
|
+
code, but it is not open source. This agreement grants no rights
|
|
18
|
+
beyond those it states.
|
|
18
19
|
|
|
19
20
|
## 1. Definitions
|
|
20
21
|
|
|
21
22
|
- The **Software** is any software we publish under the Quario name,
|
|
22
23
|
including the `quario` and `@quario/*` packages, except where a
|
|
23
24
|
product states its own terms. Each release states the edition of this
|
|
24
|
-
agreement that governs it.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
agreement that governs it. A license key bought under this edition
|
|
26
|
+
licenses every release it covers under this edition, including
|
|
27
|
+
releases that shipped with an earlier edition.
|
|
28
|
+
- **You** means the individual who exercises rights under this
|
|
29
|
+
agreement. Where that individual acts for a company or other
|
|
30
|
+
organization, **you** also means that organization. In that case the
|
|
31
|
+
organization is the licensee, and the rights and limits in this
|
|
32
|
+
agreement apply to it as a whole.
|
|
29
33
|
- An **Application** is software you create that incorporates the
|
|
30
|
-
Software and adds substantial functionality beyond it. A
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
Software and adds substantial functionality beyond it. A service you
|
|
35
|
+
operate that embeds the Software and renders reports for your End
|
|
36
|
+
Users is an Application, even if it does little more than render.
|
|
37
|
+
- A **Deployment** is one production service, application, bot, worker
|
|
38
|
+
or MCP server that runs the Software under your control. All replicas
|
|
39
|
+
of it together are one Deployment. A service that only passes render
|
|
40
|
+
requests to another service does not run the Software, so it is not
|
|
41
|
+
a Deployment. A service that a host or contractor operates on your
|
|
42
|
+
behalf stays under your control. Development, staging, CI and test
|
|
43
|
+
environments are not Deployments. We never count the people, agents
|
|
44
|
+
or renders that a Deployment serves.
|
|
45
|
+
- A **License Key** is the signed key that evidences the license of
|
|
46
|
+
one Deployment. You buy it from us or from an authorized reseller.
|
|
42
47
|
- **End Users** are people who interact with the Software only through
|
|
43
|
-
an Application
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
rights come from section 5.
|
|
48
|
+
an Application that runs in a Deployment. They run it, view the
|
|
49
|
+
reports it produces, or supply parameters, input or report
|
|
50
|
+
definitions to it. End Users do not buy a license. Their rights come
|
|
51
|
+
from section 5.
|
|
48
52
|
|
|
49
53
|
## 2. Acceptance
|
|
50
54
|
|
|
51
|
-
This file is our standing offer. Anyone who exercises the
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
This file is our standing offer. Anyone who exercises the rights in
|
|
56
|
+
section 3 accepts them, subject to their conditions. All other rights
|
|
57
|
+
start when you buy a License Key. Where a person buys for an
|
|
58
|
+
organization, that person states that they have the authority to
|
|
59
|
+
accept this agreement on the organization's behalf. The acceptance
|
|
60
|
+
then covers the whole organization. If you do not accept this
|
|
57
61
|
agreement, do not use the Software.
|
|
58
62
|
|
|
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.
|
|
63
|
+
## 3. Evaluation and development
|
|
110
64
|
|
|
111
|
-
You may
|
|
65
|
+
You may download, install and use the Software free of charge in any
|
|
66
|
+
environment that is not a Deployment. That includes evaluation,
|
|
67
|
+
prototypes, proofs of concept, and the development, staging, CI and
|
|
68
|
+
test environments of an Application. This right covers the full
|
|
69
|
+
feature set, needs no key, and has no time limit. The Software marks
|
|
70
|
+
output that no License Key covers, as section 6 describes.
|
|
112
71
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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.
|
|
72
|
+
This right does not let you use the Software in live business
|
|
73
|
+
operations, or expose it in services that third parties use. Each
|
|
74
|
+
Deployment needs a License Key under section 4.
|
|
75
|
+
|
|
76
|
+
## 4. Deployment licenses
|
|
123
77
|
|
|
124
|
-
|
|
125
|
-
|
|
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.
|
|
78
|
+
Each Deployment needs its own License Key, from the first day it runs
|
|
79
|
+
in production. With that key in place:
|
|
131
80
|
|
|
132
|
-
|
|
81
|
+
- You may set the key in that Deployment and in the development,
|
|
82
|
+
staging, CI and test copies of that Deployment. The key then removes
|
|
83
|
+
the marking there too.
|
|
84
|
+
- One key never covers two Deployments at the same time.
|
|
85
|
+
- You may move a key to a new Deployment after the old Deployment no longer
|
|
86
|
+
runs the Software in production.
|
|
87
|
+
- Contractors who work for you may use your keys for that work. They
|
|
88
|
+
may not use your keys for work they do for other clients.
|
|
133
89
|
|
|
134
|
-
|
|
135
|
-
section 4. Key checks run offline.
|
|
90
|
+
## 5. Use and pass-through
|
|
136
91
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
92
|
+
With a License Key under section 4 in place, you may operate the
|
|
93
|
+
Application in its Deployment **royalty-free**. We charge no fees for
|
|
94
|
+
servers, replicas, requests, renders, agents or End Users. You may
|
|
95
|
+
embed your License Key in the Application.
|
|
141
96
|
|
|
142
|
-
|
|
97
|
+
We grant you the right to pass through to each End User a limited,
|
|
98
|
+
non-exclusive, non-transferable right to use the Software only as
|
|
99
|
+
embedded in the Application that runs in your Deployment. You must not
|
|
100
|
+
let End Users extract the Software from the Application or use it to
|
|
101
|
+
develop software of their own.
|
|
102
|
+
|
|
103
|
+
Calling applications that only request renders from your Deployment do
|
|
104
|
+
not need to incorporate the Software. People who use those applications
|
|
105
|
+
only as End Users get the pass-through rights above.
|
|
106
|
+
|
|
107
|
+
You may not:
|
|
108
|
+
|
|
109
|
+
- Distribute the Software on its own, or as part of a product whose
|
|
110
|
+
primary purpose is to give report-engine, report-design or
|
|
111
|
+
document-generation capability to third-party developers.
|
|
112
|
+
- Expose the Software's APIs to third parties as a development tool or
|
|
113
|
+
service that replaces their own License Keys. A service you operate
|
|
114
|
+
that embeds the Software and renders for your own End Users is not
|
|
115
|
+
such a tool.
|
|
116
|
+
- Fork, mirror, repackage, rename or wrap the Software, or republish it
|
|
117
|
+
on any package registry or code-hosting platform, other than as
|
|
118
|
+
embedded in an Application.
|
|
119
|
+
|
|
120
|
+
**OEM distribution.** An Application that your customers install,
|
|
121
|
+
embed or resell, and that runs the Software outside your control, is
|
|
122
|
+
OEM distribution. A License Key does not cover OEM distribution, for
|
|
123
|
+
any number of copies. Contact us for separate terms. When you render
|
|
124
|
+
documents for your customers from a product that you host yourself,
|
|
125
|
+
that product is a Deployment, not OEM distribution.
|
|
126
|
+
|
|
127
|
+
While this agreement is in force, you may not create any tool,
|
|
128
|
+
library, product or service that competes with the Software or with
|
|
129
|
+
any other Quario product, directly or indirectly. This restriction
|
|
130
|
+
ends when this agreement terminates. You may not remove or change
|
|
131
|
+
copyright or license notices in the Software.
|
|
132
|
+
|
|
133
|
+
## 6. License Keys and output marking
|
|
134
|
+
|
|
135
|
+
The Software accepts a License Key as evidence of a Deployment's
|
|
136
|
+
license under section 4. Key checks run offline.
|
|
137
|
+
|
|
138
|
+
Each License Key states an end date. A key covers every release of the
|
|
139
|
+
Software dated on or before that end date. Without a valid key, or with
|
|
140
|
+
a key that does not cover the installed release, the Software runs with
|
|
141
|
+
its full feature set but marks its output as unlicensed. A key that is
|
|
142
|
+
void under section 7 licenses nothing, even where the installed
|
|
143
|
+
release still accepts it. Use of a key that you have no right to use
|
|
144
|
+
is a material breach of this agreement.
|
|
145
|
+
|
|
146
|
+
A later release may refuse a key that we revoked. We revoke a key when
|
|
147
|
+
we refund its payment, or when a chargeback or similar process
|
|
148
|
+
reverses its payment. We also revoke a key that
|
|
149
|
+
someone publishes for reuse by others. In that case, we give the
|
|
150
|
+
licensee a free replacement key with the same end date. When you embed a
|
|
151
|
+
key in your Application under section 5, that embedding alone does not
|
|
152
|
+
publish the key for reuse.
|
|
153
|
+
|
|
154
|
+
You may not remove, disable or bypass the key check or the output
|
|
143
155
|
marking, or distribute anything that does.
|
|
144
156
|
|
|
145
|
-
## 7. Term, updates
|
|
157
|
+
## 7. Term, updates and refunds
|
|
158
|
+
|
|
159
|
+
**No subscription.** You pay once for each License Key. The key covers
|
|
160
|
+
every release dated up to one year after you bought it, and you get
|
|
161
|
+
support until that end date. We give support through our issue tracker
|
|
162
|
+
and by email, with commercially reasonable effort. We do not guarantee
|
|
163
|
+
response times.
|
|
164
|
+
|
|
165
|
+
**Perpetual license.** Your license to the releases that a key covers
|
|
166
|
+
is **perpetual**. The Deployment may keep using those releases, and
|
|
167
|
+
the Application may keep running them, forever, at no further cost.
|
|
168
|
+
Releases dated after the key's end date need a new key.
|
|
146
169
|
|
|
147
|
-
|
|
148
|
-
|
|
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.
|
|
170
|
+
**Renewal.** Renewal is a new purchase of a new key. It never occurs
|
|
171
|
+
automatically, and we never charge you without a new order.
|
|
152
172
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
173
|
+
**Refunds and reversals.** If we refund a payment, or a chargeback or
|
|
174
|
+
similar process reverses a payment, each key that the payment
|
|
175
|
+
bought is void. A partial refund states the keys that it voids. A void
|
|
176
|
+
key gives you no rights, including no perpetual license. You keep the
|
|
177
|
+
rights of section 3, and this agreement continues. Use of a void key in
|
|
178
|
+
a Deployment is a material breach of this agreement. Keys that the
|
|
179
|
+
refund or reversal does not void stay valid. If a payment dispute is
|
|
180
|
+
decided in our favour, we restore the license with a reissued key.
|
|
160
181
|
|
|
161
182
|
## 8. Source availability
|
|
162
183
|
|
|
163
|
-
The Software's packages ship with readable source code so
|
|
184
|
+
The Software's packages ship with readable source code, so you can
|
|
164
185
|
review and debug what you run. Readable source does not grant extra
|
|
165
|
-
rights
|
|
166
|
-
|
|
167
|
-
|
|
186
|
+
rights. This agreement governs all use of the source. You may publish
|
|
187
|
+
the Software, modified or not, only as part of an Application under
|
|
188
|
+
section 5.
|
|
168
189
|
|
|
169
|
-
## 9. Ownership, trademarks
|
|
190
|
+
## 9. Ownership, trademarks and feedback
|
|
170
191
|
|
|
171
|
-
We license the Software
|
|
172
|
-
it.
|
|
173
|
-
are yours. "Quario" and the Quario logo are our trademarks
|
|
174
|
-
agreement grants no rights to use them
|
|
175
|
-
If you send us feedback or suggestions, we may use them
|
|
176
|
-
obligation.
|
|
192
|
+
We license the Software. We keep all intellectual property rights in
|
|
193
|
+
it. The report definitions, data and output that you create with the
|
|
194
|
+
Software are yours. "Quario" and the Quario logo are our trademarks.
|
|
195
|
+
This agreement grants no rights to use them, except to identify the
|
|
196
|
+
Software. If you send us feedback or suggestions, we may use them
|
|
197
|
+
without obligation.
|
|
177
198
|
|
|
178
199
|
## 10. Warranty and liability
|
|
179
200
|
|
|
@@ -181,11 +202,11 @@ We provide the Software as is. To the fullest extent the law allows, we
|
|
|
181
202
|
disclaim all warranties and conditions, express or implied, including
|
|
182
203
|
merchantability, fitness for a particular purpose, and non-infringement.
|
|
183
204
|
|
|
184
|
-
To the fullest extent the law allows
|
|
205
|
+
To the fullest extent the law allows, we are not liable for indirect or
|
|
185
206
|
consequential damages, loss of profits, loss of data, or business
|
|
186
|
-
interruption
|
|
207
|
+
interruption. Our total aggregate liability under this agreement is
|
|
187
208
|
limited to the greater of EUR 100 and the fees you paid us in the
|
|
188
|
-
twelve months
|
|
209
|
+
twelve months before the event that gave rise to the claim. These
|
|
189
210
|
limitations do not apply where the damage results from the intent or
|
|
190
211
|
deliberate recklessness (opzet of bewuste roekeloosheid) of us or our
|
|
191
212
|
management, or where mandatory law does not permit them.
|
|
@@ -193,23 +214,24 @@ management, or where mandatory law does not permit them.
|
|
|
193
214
|
## 11. Termination and survival
|
|
194
215
|
|
|
195
216
|
If you materially breach this agreement, we may terminate it by notice.
|
|
196
|
-
If
|
|
197
|
-
notice to remedy it
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
survives with
|
|
202
|
-
Sections 8 through 12 survive any termination.
|
|
217
|
+
If you can remedy the breach, we first give you 30 days from that
|
|
218
|
+
notice to remedy it. Termination then occurs only if the breach
|
|
219
|
+
continues. On termination, the licenses that this agreement grants end.
|
|
220
|
+
The perpetual license to the releases that a key covers survives
|
|
221
|
+
termination only if the end date of that key passed before the breach.
|
|
222
|
+
Section 5 survives with it, as far as you need section 5 to use that
|
|
223
|
+
license. Sections 8 through 12 survive any termination.
|
|
203
224
|
|
|
204
225
|
## 12. General
|
|
205
226
|
|
|
206
|
-
Dutch law governs this agreement.
|
|
207
|
-
|
|
208
|
-
consent, except to a successor of your whole business
|
|
209
|
-
to a successor of ours. If a provision is unenforceable,
|
|
210
|
-
stays in force.
|
|
211
|
-
agreement, together with your order for
|
|
212
|
-
about the Software, unless we agree
|
|
227
|
+
Dutch law governs this agreement. The competent court for our
|
|
228
|
+
registered office decides disputes. You may not assign this agreement
|
|
229
|
+
without our consent, except to a successor of your whole business. We
|
|
230
|
+
may assign it to a successor of ours. If a provision is unenforceable,
|
|
231
|
+
the remainder stays in force. If we do not enforce a provision, we do
|
|
232
|
+
not waive it. This agreement, together with your order for License
|
|
233
|
+
Keys, is the entire agreement about the Software, unless we agree
|
|
234
|
+
otherwise in writing.
|
|
213
235
|
|
|
214
236
|
---
|
|
215
237
|
|
package/lib/format.js
CHANGED
|
@@ -94,7 +94,7 @@ let asMoney = (value, locale, decl, options, style) => {
|
|
|
94
94
|
//
|
|
95
95
|
// The form is Intl's own `dateStyle`, and it is always present: a declaration
|
|
96
96
|
// that named none resolved to `medium` (docs/adr/0056). This is the half that
|
|
97
|
-
// does not converge — the worksheet approximates it (
|
|
97
|
+
// does not converge — the worksheet approximates it (GLOSSARY.md, "Form").
|
|
98
98
|
// `date` leads for the same reason the number kinds do, so the two key shapes
|
|
99
99
|
// live beside each other and "one piece at a time" is auditable in one place.
|
|
100
100
|
/** @type {(locale: string, decl: any, zone: string) => string} */
|
package/lib/host.js
CHANGED
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Here rather than in a target for the reason `imageError` carries beside it:
|
|
8
8
|
* every target needs it, no two of them may depend on each other, and the
|
|
9
|
-
* engine is what they all already have.
|
|
10
|
-
*
|
|
9
|
+
* engine is what they all already have. The engine's own two option bags,
|
|
10
|
+
* `quario()`'s and `plan()`'s, are checked here too, by the same rule. Nothing
|
|
11
|
+
* an author writes reaches this module -- these are host API, and no located
|
|
12
|
+
* diagnostic is involved.
|
|
11
13
|
*/
|
|
12
14
|
|
|
15
|
+
import { query } from "padvinder";
|
|
16
|
+
import { isCode } from "./style.js";
|
|
17
|
+
|
|
13
18
|
/** @type {(msg: string) => never} */
|
|
14
19
|
let fail = (msg) => {
|
|
15
20
|
throw TypeError(msg);
|
|
@@ -23,6 +28,12 @@ let fail = (msg) => {
|
|
|
23
28
|
*/
|
|
24
29
|
let plain = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
30
|
|
|
31
|
+
// Nullish is absence; anything else must be a string.
|
|
32
|
+
/** @type {(value: unknown, at: string) => void} */
|
|
33
|
+
let string = (value, at) => {
|
|
34
|
+
if (value != null && typeof value !== "string") fail(at + ": expected a string");
|
|
35
|
+
};
|
|
36
|
+
|
|
26
37
|
// Own keys only: an inherited enumerable property is the prototype's business,
|
|
27
38
|
// and naming it back at the host would name something they never wrote.
|
|
28
39
|
/** @type {(value: object, keys: readonly string[], at: string) => void} */
|
|
@@ -58,9 +69,7 @@ let PROPERTIES = ["title", "author", "subject"];
|
|
|
58
69
|
// was never written.
|
|
59
70
|
/** @type {(value: any, at: string) => void} */
|
|
60
71
|
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");
|
|
72
|
+
for (let key of Object.keys(value)) string(value[key], at + "." + key);
|
|
64
73
|
};
|
|
65
74
|
|
|
66
75
|
/**
|
|
@@ -87,3 +96,73 @@ export let hostMeta = (value, at) => {
|
|
|
87
96
|
everyString(value, at);
|
|
88
97
|
return { ...value };
|
|
89
98
|
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Every key `quario()` takes. The factory closes its set against this list,
|
|
102
|
+
* and the conformance fixture in quario's suite is held to it, so the list and
|
|
103
|
+
* `QuarioOptions` cannot drift apart unnoticed (`docs/adr/0072`, amended).
|
|
104
|
+
*/
|
|
105
|
+
export let INSTANCE_KEYS = ["query", "license", "locale", "currency", "timeZone", "schemes"];
|
|
106
|
+
|
|
107
|
+
// `Intl` owns which locales and zones exist, so it is asked rather than
|
|
108
|
+
// copied. What it refuses is a RangeError, which here is the host's mistake
|
|
109
|
+
// and wears the host-mistake class.
|
|
110
|
+
/** @type {(value: unknown, at: string, probe: (value: any) => unknown) => void} */
|
|
111
|
+
let intl = (value, at, probe) => {
|
|
112
|
+
if (value == null) return;
|
|
113
|
+
try {
|
|
114
|
+
probe(value);
|
|
115
|
+
} catch {
|
|
116
|
+
fail(at + ": " + JSON.stringify(value) + " is not one Intl accepts");
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The engine factory's own options, refused at the call like a target's. The
|
|
122
|
+
* three names that present `format` are checked for validity and not just
|
|
123
|
+
* type, because a typo there does not fail: every formatted cell prints its
|
|
124
|
+
* raw value instead. A cell's own `style.currency` keeps failing soft -- that
|
|
125
|
+
* is the authoring surface, and this is the host's.
|
|
126
|
+
*
|
|
127
|
+
* `currency` takes the author's gate, three upper-case letters, because the
|
|
128
|
+
* xlsx target splices the code into a number format and an engine default the
|
|
129
|
+
* gate never saw would be the one code that skipped it (`docs/adr/0041`).
|
|
130
|
+
*
|
|
131
|
+
* `schemes` keeps its own null posture: a `null` allowlist is a host saying
|
|
132
|
+
* something, and reading it as the default would widen what they restricted
|
|
133
|
+
* (`docs/adr/0085`).
|
|
134
|
+
*
|
|
135
|
+
* `query` is padvinder's, and so is the list of what it may carry: compiling
|
|
136
|
+
* one trivial query here raises padvinder's own refusal at the call, where it
|
|
137
|
+
* would otherwise wait for the first query a document compiles.
|
|
138
|
+
*
|
|
139
|
+
* @param {any} options The `quario()` argument.
|
|
140
|
+
*/
|
|
141
|
+
export let instanceOptions = (options) => {
|
|
142
|
+
let o = hostOptions(options, INSTANCE_KEYS, "options") ?? {};
|
|
143
|
+
query("$", undefined, o.query);
|
|
144
|
+
for (let key of ["license", "locale", "currency", "timeZone"]) string(o[key], "options." + key);
|
|
145
|
+
intl(o.locale, "options.locale", (v) => Intl.getCanonicalLocales(v));
|
|
146
|
+
intl(o.timeZone, "options.timeZone", (v) => Intl.DateTimeFormat("en", { timeZone: v }));
|
|
147
|
+
if (o.currency != null && !isCode(o.currency))
|
|
148
|
+
fail("options.currency: expected a three-letter ISO 4217 code");
|
|
149
|
+
let schemes = o.schemes;
|
|
150
|
+
if (schemes === undefined) return;
|
|
151
|
+
let listed =
|
|
152
|
+
Array.isArray(schemes) &&
|
|
153
|
+
schemes.every((scheme) => typeof scheme === "string" && /^[a-z][a-z0-9+.-]*:$/.test(scheme));
|
|
154
|
+
if (!listed)
|
|
155
|
+
fail("options.schemes: expected an array of lower-case URL schemes, each ending in a colon");
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The third argument of an instance's `plan()`, closed like the factory's, so
|
|
160
|
+
* a misspelt `targets` is a throw rather than a plan that checked nothing and
|
|
161
|
+
* said so by saying nothing.
|
|
162
|
+
*
|
|
163
|
+
* @param {any} options The `plan()` options.
|
|
164
|
+
*/
|
|
165
|
+
export let planOptions = (options) => {
|
|
166
|
+
let targets = hostOptions(options, ["targets"], "options")?.targets;
|
|
167
|
+
if (targets != null && !Array.isArray(targets)) fail("options.targets: expected an array");
|
|
168
|
+
};
|
package/lib/image.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What the engine reads out of an image item's bytes: which format they are,
|
|
3
3
|
* and how big the picture is. Read, never decoded -- a magic number, a PNG's
|
|
4
|
-
* IHDR
|
|
4
|
+
* IHDR, a JPEG's frame header and an SVG's root tag, and nothing here looks at
|
|
5
|
+
* a pixel or draws a shape.
|
|
5
6
|
*
|
|
6
7
|
* Both answers ride on the image event, so no target reads these headers
|
|
7
8
|
* again. That is the whole of `docs/adr/0067`: two targets used to keep a
|
|
@@ -16,6 +17,45 @@
|
|
|
16
17
|
let PNG = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
17
18
|
let JPEG = [0xff, 0xd8, 0xff];
|
|
18
19
|
|
|
20
|
+
// SVG has no magic number: it is text, named by its root element. Fatal, so
|
|
21
|
+
// bytes that are not UTF-8 are not text at all. The decoder drops a leading
|
|
22
|
+
// BOM on its own.
|
|
23
|
+
let UTF8 = new TextDecoder("utf-8", { fatal: true });
|
|
24
|
+
|
|
25
|
+
// What may stand before the root, one at a time: whitespace, then a
|
|
26
|
+
// processing instruction (the XML declaration is one), a comment or a
|
|
27
|
+
// doctype with its optional internal subset. Sticky, so each match starts
|
|
28
|
+
// where the last one ended and nothing is skipped over.
|
|
29
|
+
let PROLOG = /\s*(?:<\?[\s\S]*?\?>|<!--[\s\S]*?-->|<!DOCTYPE[^[>]*(?:\[[\s\S]*?\])?\s*>)/y;
|
|
30
|
+
|
|
31
|
+
// The root's start tag, whose attributes are the group. A quoted value may
|
|
32
|
+
// hold a `>`, so the tag ends at the first one outside quotes. `<svgx` and a
|
|
33
|
+
// prefixed `<s:svg` are other elements.
|
|
34
|
+
let ROOT = /\s*<svg(?=[\s/>])((?:[^>"']|"[^"]*"|'[^']*')*)>/y;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The text inside the root's start tag, when the bytes are UTF-8 whose first
|
|
38
|
+
* element after the prolog is `<svg`, else null. An HTML page holding an SVG
|
|
39
|
+
* further in is not one: the root decides (SCHEMA.md, "Image item").
|
|
40
|
+
*
|
|
41
|
+
* @type {(bytes: Uint8Array) => string | null}
|
|
42
|
+
*/
|
|
43
|
+
let svgRoot = (bytes) => {
|
|
44
|
+
let text;
|
|
45
|
+
try {
|
|
46
|
+
text = UTF8.decode(bytes);
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// A sticky test that fails resets `lastIndex` to zero, so the end of the
|
|
51
|
+
// last prolog part is kept apart from it.
|
|
52
|
+
let at = 0;
|
|
53
|
+
PROLOG.lastIndex = 0;
|
|
54
|
+
while (PROLOG.test(text)) at = PROLOG.lastIndex;
|
|
55
|
+
ROOT.lastIndex = at;
|
|
56
|
+
return ROOT.exec(text)?.[1] ?? null;
|
|
57
|
+
};
|
|
58
|
+
|
|
19
59
|
/**
|
|
20
60
|
* The image seam: name the format a `source` expression's bytes hold, or null
|
|
21
61
|
* when they are not image bytes at all -- including when they are not bytes.
|
|
@@ -23,12 +63,13 @@ let JPEG = [0xff, 0xd8, 0xff];
|
|
|
23
63
|
* so no consumer repeats it.
|
|
24
64
|
*
|
|
25
65
|
* @param {any} bytes The value a `source` expression yielded.
|
|
26
|
-
* @returns {"png" | "jpeg" | null} The format, or null when unreadable.
|
|
66
|
+
* @returns {"png" | "jpeg" | "svg" | null} The format, or null when unreadable.
|
|
27
67
|
*/
|
|
28
68
|
export function sniff(bytes) {
|
|
29
69
|
if (!(bytes instanceof Uint8Array)) return null;
|
|
30
70
|
if (PNG.every((byte, i) => bytes[i] === byte)) return "png";
|
|
31
71
|
if (JPEG.every((byte, i) => bytes[i] === byte)) return "jpeg";
|
|
72
|
+
if (svgRoot(bytes) != null) return "svg";
|
|
32
73
|
return null;
|
|
33
74
|
}
|
|
34
75
|
|
|
@@ -62,15 +103,67 @@ let jpegSize = (bytes) => {
|
|
|
62
103
|
return { width: 0, height: 0 };
|
|
63
104
|
};
|
|
64
105
|
|
|
106
|
+
// A plain decimal, as XML's attribute grammar writes one. Stricter than
|
|
107
|
+
// `Number`, which would also read hex, `Infinity` and the empty string.
|
|
108
|
+
let NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
109
|
+
|
|
110
|
+
/** @type {(text: string) => number} */
|
|
111
|
+
let number = (text) => (NUMBER.test(text) ? Number(text) : NaN);
|
|
112
|
+
|
|
113
|
+
// The absolute units, as how many of each make an inch. Pixels are the
|
|
114
|
+
// unit the event carries, so a unitless length and `px` pass as they are,
|
|
115
|
+
// and the rest convert at the 96 dpi the size is read at everywhere else.
|
|
116
|
+
let PER_INCH = new Map([
|
|
117
|
+
["pt", 72],
|
|
118
|
+
["in", 1],
|
|
119
|
+
["cm", 2.54],
|
|
120
|
+
["mm", 25.4],
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
/** @type {(value: string | undefined) => number | null} */
|
|
124
|
+
let length = (value) => {
|
|
125
|
+
let [, amount = "", unit = ""] = value?.trim().match(/^(.*?)(px|pt|in|cm|mm)?$/) ?? [];
|
|
126
|
+
let pixels = number(amount);
|
|
127
|
+
if (Number.isNaN(pixels)) return null;
|
|
128
|
+
let perInch = PER_INCH.get(unit);
|
|
129
|
+
return perInch ? (pixels / perInch) * 96 : pixels;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// One attribute of the root's start tag, double- or single-quoted.
|
|
133
|
+
let ATTRIBUTE = /([^\s=/]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
134
|
+
|
|
135
|
+
// An SVG's size is the root's `width` and `height` when both are absolute
|
|
136
|
+
// lengths, else its `viewBox`'s last two numbers. A percentage or a font-
|
|
137
|
+
// relative length is no size: it depends on a container the file never sees.
|
|
138
|
+
/** @type {(bytes: Uint8Array) => { width: number, height: number }} */
|
|
139
|
+
let svgSize = (bytes) => {
|
|
140
|
+
// `sniff` named these bytes SVG, so the root is there to read.
|
|
141
|
+
let root = /** @type {string} */ (svgRoot(bytes));
|
|
142
|
+
let attributes = new Map(
|
|
143
|
+
[...root.matchAll(ATTRIBUTE)].map(([, name, double, single]) => [name, double ?? single]),
|
|
144
|
+
);
|
|
145
|
+
let width = length(attributes.get("width"));
|
|
146
|
+
let height = length(attributes.get("height"));
|
|
147
|
+
if (width != null && height != null) return { width, height };
|
|
148
|
+
let box =
|
|
149
|
+
attributes
|
|
150
|
+
.get("viewBox")
|
|
151
|
+
?.trim()
|
|
152
|
+
.split(/[\s,]+/)
|
|
153
|
+
.map(number) ?? [];
|
|
154
|
+
return box.length === 4 ? { width: box[2], height: box[3] } : { width: 0, height: 0 };
|
|
155
|
+
};
|
|
156
|
+
|
|
65
157
|
// One reader per format `sniff` names.
|
|
66
158
|
/** @type {Record<string, (bytes: Uint8Array) => { width: number, height: number }>} */
|
|
67
|
-
let READERS = { png: pngSize, jpeg: jpegSize };
|
|
159
|
+
let READERS = { png: pngSize, jpeg: jpegSize, svg: svgSize };
|
|
68
160
|
|
|
69
161
|
/**
|
|
70
162
|
* The image's own size in pixels, or null when the header does not carry one
|
|
71
163
|
* -- bytes whose magic numbers `sniff` vouched for, truncated before the
|
|
72
|
-
* dimensions
|
|
73
|
-
*
|
|
164
|
+
* dimensions, or an SVG whose root states no usable size. Read, never
|
|
165
|
+
* decoded: a PNG's IHDR, a JPEG's frame header and an SVG's root tag carry
|
|
166
|
+
* the two numbers, and nothing here looks at a pixel.
|
|
74
167
|
*
|
|
75
168
|
* The size rides on the image event beside the format, so every target places
|
|
76
169
|
* a picture from a field rather than parsing the same two headers again, and a
|
|
@@ -78,7 +171,7 @@ let READERS = { png: pngSize, jpeg: jpegSize };
|
|
|
78
171
|
* alike (`docs/adr/0067`).
|
|
79
172
|
*
|
|
80
173
|
* @param {Uint8Array} bytes The image file.
|
|
81
|
-
* @param {"png" | "jpeg"} format The format `sniff` named.
|
|
174
|
+
* @param {"png" | "jpeg" | "svg"} format The format `sniff` named.
|
|
82
175
|
* @returns {{ width: number, height: number } | null} The size, if readable.
|
|
83
176
|
*/
|
|
84
177
|
export function dimensions(bytes, format) {
|
|
@@ -86,7 +179,10 @@ export function dimensions(bytes, format) {
|
|
|
86
179
|
// format `sniff` learns later and this table does not would throw here
|
|
87
180
|
// rather than be read silently as the one on the ternary's else branch.
|
|
88
181
|
let size = READERS[format](bytes);
|
|
89
|
-
// A zero is how
|
|
90
|
-
// absent byte reads as zero, and no image is zero wide.
|
|
91
|
-
|
|
182
|
+
// A zero is how a raster reader says "the header stopped before this" -- an
|
|
183
|
+
// absent byte reads as zero, and no image is zero wide. The SVG reader
|
|
184
|
+
// hands back whatever the root said, so a negative, a NaN and an infinity
|
|
185
|
+
// fail here too.
|
|
186
|
+
let placeable = (/** @type {number} */ n) => n > 0 && n < Infinity;
|
|
187
|
+
return placeable(size.width) && placeable(size.height) ? size : null;
|
|
92
188
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -161,6 +161,21 @@ export interface StyleDeclarations extends BoxDeclarations {
|
|
|
161
161
|
spaceAfter?: ExpressionValue<Points>;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/** How a group orders and cuts its instances. */
|
|
165
|
+
export interface Instances {
|
|
166
|
+
/**
|
|
167
|
+
* Order the instances. Each key reads the group's own handle once per
|
|
168
|
+
* instance, with `@` and `run` unbound.
|
|
169
|
+
*/
|
|
170
|
+
sort?: SortKey[];
|
|
171
|
+
/**
|
|
172
|
+
* Keep this many instances after `sort`. Integer >= 1. A dropped instance
|
|
173
|
+
* never renders, so no runner steps over it, but the enclosing aggregates
|
|
174
|
+
* still count it.
|
|
175
|
+
*/
|
|
176
|
+
take?: Count;
|
|
177
|
+
}
|
|
178
|
+
|
|
164
179
|
export interface SortKey {
|
|
165
180
|
by: Expression;
|
|
166
181
|
dir?: "asc" | "desc";
|
|
@@ -349,9 +364,9 @@ export type ImageStyleDeclarations = Pick<
|
|
|
349
364
|
export type ReportStyleDeclarations = Pick<StyleDeclarations, "family" | "size">;
|
|
350
365
|
|
|
351
366
|
/**
|
|
352
|
-
* A host-supplied
|
|
367
|
+
* A host-supplied graphic in the band flow. `source` is an expression
|
|
353
368
|
* (never a literal — JSON has no way to write bytes) yielding the complete
|
|
354
|
-
* image file as a `Uint8Array` of PNG or
|
|
369
|
+
* image file as a `Uint8Array` of PNG, JPEG or SVG bytes; nullish skips the item
|
|
355
370
|
* silently, anything else invalid is a located render error.
|
|
356
371
|
*/
|
|
357
372
|
export interface ImageItem {
|
|
@@ -594,6 +609,12 @@ export interface Group {
|
|
|
594
609
|
*/
|
|
595
610
|
columns?: ColumnCount;
|
|
596
611
|
sort?: SortKey[];
|
|
612
|
+
/**
|
|
613
|
+
* Order and cut this group's instances, as the group's own `sort` and
|
|
614
|
+
* `take` order and cut the rows inside each. Absent, every instance renders
|
|
615
|
+
* in first-seen order.
|
|
616
|
+
*/
|
|
617
|
+
instances?: Instances;
|
|
597
618
|
/**
|
|
598
619
|
* Keep this many rows after this group's `sort`. Integer >= 1. Rows past
|
|
599
620
|
* the count are dropped from aggregates and runners, unlike `visible`.
|
|
@@ -931,7 +952,7 @@ export interface ItemEvent {
|
|
|
931
952
|
}
|
|
932
953
|
|
|
933
954
|
/** The format the engine sniffed from an image's bytes. */
|
|
934
|
-
export type ImageFormat = "png" | "jpeg";
|
|
955
|
+
export type ImageFormat = "png" | "jpeg" | "svg";
|
|
935
956
|
|
|
936
957
|
export interface ImageEvent {
|
|
937
958
|
type: "image";
|
|
@@ -940,7 +961,10 @@ export interface ImageEvent {
|
|
|
940
961
|
path: string;
|
|
941
962
|
/** The bytes exactly as the `source` expression yielded them. */
|
|
942
963
|
bytes: Uint8Array;
|
|
943
|
-
/**
|
|
964
|
+
/**
|
|
965
|
+
* Sniffed from the bytes — a PNG's or a JPEG's magic number, an SVG's root
|
|
966
|
+
* element — so no consumer repeats it.
|
|
967
|
+
*/
|
|
944
968
|
format: ImageFormat;
|
|
945
969
|
/**
|
|
946
970
|
* The image's own width in pixels, read from the same header. Bytes too
|
|
@@ -1262,13 +1286,21 @@ export interface Quario {
|
|
|
1262
1286
|
* The one traversal, whole: compiled report (null on problems), structured
|
|
1263
1287
|
* problems, advisory warnings, and per-node anchor sets — one descent per
|
|
1264
1288
|
* edit for a host that validates and renders in a loop.
|
|
1289
|
+
*
|
|
1290
|
+
* @throws {TypeError} For an unknown `options` key, or a `targets` that is
|
|
1291
|
+
* not an array — closed like the factory's own options.
|
|
1265
1292
|
*/
|
|
1266
1293
|
plan(schema: unknown, functions?: FunctionRegistry, options?: PlanOptions): Plan;
|
|
1267
1294
|
}
|
|
1268
1295
|
|
|
1269
1296
|
/**
|
|
1270
1297
|
* Create a configured instance: host-level options (license key, query
|
|
1271
|
-
* budgets).
|
|
1298
|
+
* budgets, presentation, the `href` allowlist).
|
|
1299
|
+
*
|
|
1300
|
+
* @throws {TypeError} At the call, for an unknown option key, a value of the
|
|
1301
|
+
* wrong type, a `locale` or `timeZone` that `Intl` refuses, or a `currency`
|
|
1302
|
+
* that is not three upper-case letters. Nullish is absence, except for
|
|
1303
|
+
* `schemes`. The `query` budgets are refused by padvinder, in its own words.
|
|
1272
1304
|
*/
|
|
1273
1305
|
export function quario(options?: QuarioOptions): Quario;
|
|
1274
1306
|
|
package/lib/index.js
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { relocate as relocateQuery } from "padvinder";
|
|
16
16
|
import { LEAD_REFUSED, isLead, leadPath } from "./band-height.js";
|
|
17
|
-
import {
|
|
17
|
+
import { instanceOptions, planOptions } from "./host.js";
|
|
18
|
+
import { MARKING, TRUST, verify } from "./license.js";
|
|
18
19
|
import { err, fault, locate } from "./locate.js";
|
|
19
20
|
import { BLOCKED, NAME, record } from "./names.js";
|
|
20
21
|
import { checkParamType, jsonCopy, plan } from "./plan.js";
|
|
@@ -152,7 +153,7 @@ let calling = (target) => (typeof target?.target === "string" ? target.target :
|
|
|
152
153
|
/** @type {readonly any[]} */
|
|
153
154
|
let NO_TARGETS = Object.freeze([]);
|
|
154
155
|
/** @type {(opts: any) => readonly any[]} */
|
|
155
|
-
let named = (opts) =>
|
|
156
|
+
let named = (opts) => opts?.targets ?? NO_TARGETS;
|
|
156
157
|
|
|
157
158
|
// What a caller asked to check beyond the document: the required declarations
|
|
158
159
|
// its targets lose.
|
|
@@ -175,7 +176,7 @@ let loss = (target, name) => {
|
|
|
175
176
|
let unmet = (required, targets) =>
|
|
176
177
|
required.flatMap((name) => targets.map((target) => loss(target, name)).filter(Boolean));
|
|
177
178
|
|
|
178
|
-
// The per-node anchor sets, as frozen host data (
|
|
179
|
+
// The per-node anchor sets, as frozen host data (GLOSSARY.md, "Freeze"): which
|
|
179
180
|
// anchors and group handles each compiled source reads, keyed by schema path.
|
|
180
181
|
/** @type {(anchors: Map<string, Set<string>>) => any} */
|
|
181
182
|
let freezeAnchors = (anchors) =>
|
|
@@ -484,21 +485,6 @@ function assemble(planned, schema, marking, options) {
|
|
|
484
485
|
|
|
485
486
|
/** @typedef {import("./license.js").LicenseInfo} LicenseInfo */
|
|
486
487
|
|
|
487
|
-
// The `href` allowlist, checked once at construction rather than per cell.
|
|
488
|
-
// It **throws** where every other host option is lenient: an allowlist is a
|
|
489
|
-
// security control, and one the engine quietly replaced with its own default
|
|
490
|
-
// would be a host believing it had restricted something it had not
|
|
491
|
-
// (docs/adr/0085).
|
|
492
|
-
/** @type {(schemes: unknown) => void} */
|
|
493
|
-
let checkSchemes = (schemes) => {
|
|
494
|
-
if (schemes === undefined) return;
|
|
495
|
-
let listed =
|
|
496
|
-
Array.isArray(schemes) &&
|
|
497
|
-
schemes.every((scheme) => typeof scheme === "string" && /^[a-z][a-z0-9+.-]*:$/.test(scheme));
|
|
498
|
-
if (!listed)
|
|
499
|
-
throw Error("schemes: expected an array of lower-case URL schemes, each ending in a colon");
|
|
500
|
-
};
|
|
501
|
-
|
|
502
488
|
/**
|
|
503
489
|
* Create a configured quario instance: the host-level options — license key,
|
|
504
490
|
* query budgets. The key is verified once, here, and `license` settles with
|
|
@@ -507,19 +493,18 @@ let checkSchemes = (schemes) => {
|
|
|
507
493
|
* ("Instances and targets").
|
|
508
494
|
*
|
|
509
495
|
* @param {{query?: {maxNodes?: number, maxDepth?: number, maxResults?: number},
|
|
510
|
-
* license?: string,
|
|
511
|
-
* locale?: string, currency?: string, timeZone?: string,
|
|
496
|
+
* license?: string, locale?: string, currency?: string, timeZone?: string,
|
|
512
497
|
* schemes?: readonly string[]}} [options]
|
|
513
|
-
* Host configuration. `
|
|
514
|
-
* `locale` / `currency` / `timeZone` present `format` (docs/adr/0041), and
|
|
498
|
+
* Host configuration, its key set closed (docs/adr/0072). `locale` / `currency` / `timeZone` present `format` (docs/adr/0041), and
|
|
515
499
|
* `schemes` is the `href` allowlist (docs/adr/0085).
|
|
516
500
|
* @returns {{license: Promise<LicenseInfo>,
|
|
517
501
|
* report: (schema: any, funcs?: Record<string, Function>) => any,
|
|
518
|
-
* plan: (schema: any, funcs?: Record<string, Function
|
|
502
|
+
* plan: (schema: any, funcs?: Record<string, Function>, opts?: any) => any}}
|
|
503
|
+
* The instance.
|
|
519
504
|
*/
|
|
520
505
|
export function quario(options) {
|
|
521
|
-
|
|
522
|
-
let { licensed, settled } = verify(options?.license, options?.
|
|
506
|
+
instanceOptions(options);
|
|
507
|
+
let { licensed, settled } = verify(options?.license, /** @type {any} */ (options)?.[TRUST]);
|
|
523
508
|
// The wording is the instance's, where the key is; the stream is what
|
|
524
509
|
// states it, so nothing wraps a compiled stream to reach its first event.
|
|
525
510
|
/** @type {() => string | false} */
|
|
@@ -571,7 +556,7 @@ export function quario(options) {
|
|
|
571
556
|
report(schema, funcs) {
|
|
572
557
|
return wrap(events(schema, funcs, options, marking));
|
|
573
558
|
},
|
|
574
|
-
// The plan (
|
|
559
|
+
// The plan (GLOSSARY.md): both readings of the one descent, kept.
|
|
575
560
|
// `report()` and `validate()` name what a caller wanted from it; this
|
|
576
561
|
// hands over the whole thing — the compiled report (null while the
|
|
577
562
|
// document has problems), every problem structurally, and the per-node
|
|
@@ -586,6 +571,7 @@ export function quario(options) {
|
|
|
586
571
|
// makes the whole marking additive.
|
|
587
572
|
/** @type {(schema: any, funcs?: Record<string, Function>, opts?: any) => any} */
|
|
588
573
|
plan(schema, funcs, opts) {
|
|
574
|
+
planOptions(opts);
|
|
589
575
|
let planned = plan(schema, funcs, options);
|
|
590
576
|
// After the document's own, so a problem list still opens with what is
|
|
591
577
|
// wrong with the report rather than with where it is going.
|
package/lib/license.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// though nothing reassigns them. A key is valid for every version released on
|
|
12
12
|
// or before its `to` (LICENSE section 7), so validity compares ISO date
|
|
13
13
|
// strings and never reads a clock — accepted output stays accepted.
|
|
14
|
-
let RELEASE = "2026-10-
|
|
14
|
+
let RELEASE = "2026-10-08"; // 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.
|
|
@@ -24,6 +24,18 @@ let PUBKEY =
|
|
|
24
24
|
// draws it through a base-14 face, and the em dash is the one character here
|
|
25
25
|
// that had to be checked. The assertion that would catch a bad one lives in
|
|
26
26
|
// that target's suite (`packages/pdf/test/pdf.test.js`), not this package's.
|
|
27
|
+
/**
|
|
28
|
+
* The key a test suite writes its keypair under, in place of the shipped one.
|
|
29
|
+
* A symbol rather than an option name: the production private key never
|
|
30
|
+
* enters the repo, so the suites verify against freshly generated pairs, and
|
|
31
|
+
* a named option would hand every host the same seam -- sign your own key,
|
|
32
|
+
* pass your own public key. The closed option check reads string keys only,
|
|
33
|
+
* and nothing the package exports reaches this module, so a host has no
|
|
34
|
+
* spelling for it. The suites import it through the repository's `#license`
|
|
35
|
+
* specifier, which resolves to this file and not a copy of it.
|
|
36
|
+
*/
|
|
37
|
+
export let TRUST = Symbol("quario.trust");
|
|
38
|
+
|
|
27
39
|
export let MARKING = "Unlicensed evaluation — getquario.com";
|
|
28
40
|
|
|
29
41
|
let ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -107,8 +119,7 @@ let failMsg = (error) => (error instanceof Error ? error.message : String(error)
|
|
|
107
119
|
* @param {string | undefined} key The instance's `license` option.
|
|
108
120
|
* @param {{publicKey?: string, release?: string}} [trust] What this instance
|
|
109
121
|
* verifies against, defaulting to the shipped key and release date. Internal
|
|
110
|
-
* —
|
|
111
|
-
* so suites verify against freshly generated pairs.
|
|
122
|
+
* — the `TRUST` seam above, never an option a host can name.
|
|
112
123
|
* @returns {{licensed: () => boolean, settled: Promise<LicenseInfo>}}
|
|
113
124
|
*/
|
|
114
125
|
export let verify = (key, trust) => {
|
package/lib/plan.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* constructs, so they must not appear even in a comment.)
|
|
11
11
|
*
|
|
12
12
|
* It is a traversal, never a walk: that word belongs to the event stream and
|
|
13
|
-
* its driver (
|
|
13
|
+
* its driver (GLOSSARY.md). Six layers hold it, each a factory named for what it
|
|
14
14
|
* returns and taking the layers it builds on, composed by `plan()` at the foot
|
|
15
15
|
* of the file:
|
|
16
16
|
*
|
|
@@ -192,6 +192,7 @@ export let KEY_SETS = Object.freeze({
|
|
|
192
192
|
"reset",
|
|
193
193
|
"columns",
|
|
194
194
|
"sort",
|
|
195
|
+
"instances",
|
|
195
196
|
"take",
|
|
196
197
|
"aggregates",
|
|
197
198
|
"run",
|
|
@@ -199,6 +200,7 @@ export let KEY_SETS = Object.freeze({
|
|
|
199
200
|
"footer",
|
|
200
201
|
],
|
|
201
202
|
sortKey: ["by", "dir"],
|
|
203
|
+
instances: ["sort", "take"],
|
|
202
204
|
param: ["default", "type", "label"],
|
|
203
205
|
text: TEXT,
|
|
204
206
|
textSlot: [...TEXT, "width"],
|
|
@@ -644,7 +646,7 @@ let runProp = (expression, path, source) => (scope) => {
|
|
|
644
646
|
// have a text form, and that is the token seam.
|
|
645
647
|
return refuseThenable(out, path, source, ONLY_A_CELL);
|
|
646
648
|
};
|
|
647
|
-
// The [live field](../../../
|
|
649
|
+
// The [live field](../../../GLOSSARY.md#live-field): which page value a token
|
|
648
650
|
// *is*, for a target whose own format numbers pages. sjabloon offers each
|
|
649
651
|
// interpolation's expression source once while compiling, the only place the
|
|
650
652
|
// author's words still exist. Reading the source here instead is what hard
|
|
@@ -1269,9 +1271,14 @@ let readers = ({ bad, warn, attempt }, { prop, cell, parseFold }, /** @type {any
|
|
|
1269
1271
|
checkDir(spec.dir, keyPath + ".dir");
|
|
1270
1272
|
return [{ by, sign: spec.dir === "desc" ? -1 : 1 }];
|
|
1271
1273
|
};
|
|
1272
|
-
// A sort spec (array of { by, dir }) becomes a comparator over rows
|
|
1273
|
-
|
|
1274
|
-
|
|
1274
|
+
// A sort spec (array of { by, dir }) becomes a comparator over rows, or over
|
|
1275
|
+
// whatever `bind` turns into the scope its keys read: a group's `instances`
|
|
1276
|
+
// binds each instance's own group scope, where no row is current.
|
|
1277
|
+
/**
|
|
1278
|
+
* @type {(value: any, path: string, bind?: (scope: Scope, item: any) => Scope)
|
|
1279
|
+
* => ((items: any[], scope: Scope) => any[]) | null}
|
|
1280
|
+
*/
|
|
1281
|
+
let sortOf = (value, path, bind = withRow) => {
|
|
1275
1282
|
let specs = arr(value, path);
|
|
1276
1283
|
if (!specs || !specs.length) return null;
|
|
1277
1284
|
let sortKeys = specs.flatMap((spec, i) => sortKey(spec, i, path));
|
|
@@ -1287,7 +1294,7 @@ let readers = ({ bad, warn, attempt }, { prop, cell, parseFold }, /** @type {any
|
|
|
1287
1294
|
let resolved = [];
|
|
1288
1295
|
for (let row of rows) {
|
|
1289
1296
|
// One scope child per row: every key of that row reads the same one.
|
|
1290
|
-
let bound =
|
|
1297
|
+
let bound = bind(scope, row);
|
|
1291
1298
|
for (let key of sortKeys) resolved.push(key.by(bound));
|
|
1292
1299
|
}
|
|
1293
1300
|
// `sort` is stable, so a full tie leaves the indices — and with them
|
|
@@ -1603,7 +1610,7 @@ let nodes = (
|
|
|
1603
1610
|
// into a string -- and both answers ride on the event so no target reads
|
|
1604
1611
|
// the same header a second time.
|
|
1605
1612
|
let format = sniff(bytes);
|
|
1606
|
-
if (!format) badImage(fields, "expected a Uint8Array of PNG or
|
|
1613
|
+
if (!format) badImage(fields, "expected a Uint8Array of PNG, JPEG or SVG bytes");
|
|
1607
1614
|
let size = dimensions(bytes, format);
|
|
1608
1615
|
// The one thing the engine reads past the magic numbers, and so the one
|
|
1609
1616
|
// header failure every target shares: a file that cannot state its size
|
|
@@ -2196,6 +2203,27 @@ let bands = (
|
|
|
2196
2203
|
rows,
|
|
2197
2204
|
};
|
|
2198
2205
|
};
|
|
2206
|
+
/** @type {(items: any[], fn: (item: any) => any) => Generator<any>} */
|
|
2207
|
+
let lazily = function* (items, fn) {
|
|
2208
|
+
for (let item of items) yield fn(item);
|
|
2209
|
+
};
|
|
2210
|
+
// A partition's instances, opened, in the order they render, and how many
|
|
2211
|
+
// render. Without `instances.sort` that is first-seen order, opened one at a
|
|
2212
|
+
// time as the walk reaches each. With it every instance opens first,
|
|
2213
|
+
// because its key reads the handles, and the group scopes are then what it
|
|
2214
|
+
// orders. `instances.take` cuts either order the way a row `take` cuts rows,
|
|
2215
|
+
// so an instance it drops never opens a band.
|
|
2216
|
+
/** @type {(ctx: any, parts: Map<any, any[]>, scope: Scope) => { count: number, each: Iterable<any> }} */
|
|
2217
|
+
let instancesOf = (ctx, parts, scope) => {
|
|
2218
|
+
let { sort, take } = ctx.instances;
|
|
2219
|
+
/** @type {(part: [any, any[]]) => any} */
|
|
2220
|
+
let open = ([key, groupRows]) => ({ key, ...openInstance(ctx, key, groupRows, scope) });
|
|
2221
|
+
let kept = (sort ? sort(Array.from(parts, open), scope) : [...parts]).slice(
|
|
2222
|
+
0,
|
|
2223
|
+
take ?? undefined,
|
|
2224
|
+
);
|
|
2225
|
+
return { count: kept.length, each: sort ? kept : lazily(kept, open) };
|
|
2226
|
+
};
|
|
2199
2227
|
// One instance's bands, in order, as the events they yield.
|
|
2200
2228
|
/** @type {(ctx: any, groupScope: any, ordered: any[], runners: any) => Generator<any>} */
|
|
2201
2229
|
let instanceBands = function* (ctx, groupScope, ordered, runners) {
|
|
@@ -2232,11 +2260,11 @@ let bands = (
|
|
|
2232
2260
|
// run's own two edges fall on its first and its last. The run is this
|
|
2233
2261
|
// call's partition, so a nested group's edges belong to the parent
|
|
2234
2262
|
// instance holding it rather than to the document.
|
|
2235
|
-
let
|
|
2263
|
+
let { count, each } = instancesOf(ctx, parts, scope);
|
|
2264
|
+
let last = count - 1;
|
|
2236
2265
|
let index = 0;
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
let { groupScope, aggregates, rows: ordered } = openInstance(ctx, key, groupRows, scope);
|
|
2266
|
+
// A group band has no current row, so `@` stays unbound here.
|
|
2267
|
+
for (let { key, groupScope, aggregates, rows: ordered } of each) {
|
|
2240
2268
|
yield opt(
|
|
2241
2269
|
{
|
|
2242
2270
|
type: "group-start",
|
|
@@ -2262,6 +2290,19 @@ let bands = (
|
|
|
2262
2290
|
index++;
|
|
2263
2291
|
}
|
|
2264
2292
|
};
|
|
2293
|
+
/** @type {(scope: Scope, instance: any) => Scope} */
|
|
2294
|
+
let instanceScope = (_, instance) => instance.groupScope;
|
|
2295
|
+
// `instances` is the group's own `sort` and `take`, one level up: the same
|
|
2296
|
+
// two words, over its instances rather than its rows (docs/adr/0103).
|
|
2297
|
+
/** @type {(value: any, path: string) => { sort: any, take: number | null }} */
|
|
2298
|
+
let instancesSpec = (value, path) => {
|
|
2299
|
+
if (value == null || !obj(value, path)) return { sort: null, take: null };
|
|
2300
|
+
keys(value, KEY_SETS.instances, path);
|
|
2301
|
+
return {
|
|
2302
|
+
sort: sortOf(value.sort, path + ".sort", instanceScope),
|
|
2303
|
+
take: takeOf(value.take, path + ".take"),
|
|
2304
|
+
};
|
|
2305
|
+
};
|
|
2265
2306
|
/** @type {(def: any, path: string, defs: any[], index: number, detail: any, columned?: boolean) => Band} */
|
|
2266
2307
|
let compileGroup = (def, path, defs, index, detail, columned) => {
|
|
2267
2308
|
keys(def, KEY_SETS.group, path);
|
|
@@ -2279,6 +2320,7 @@ let bands = (
|
|
|
2279
2320
|
// styling a fragment of one would mean nothing anywhere it is spent.
|
|
2280
2321
|
label: def.label == null ? null : textValue(def.label, path + ".label"),
|
|
2281
2322
|
sort: sortOf(def.sort, path + ".sort"),
|
|
2323
|
+
instances: instancesSpec(def.instances, path + ".instances"),
|
|
2282
2324
|
take: takeOf(def.take, path + ".take"),
|
|
2283
2325
|
aggs: foldsOf(def.aggregates, path + ".aggregates", "aggregate", new Set(["key"])),
|
|
2284
2326
|
running: foldsOf(def.run, path + ".run", "run", null, runNames),
|
package/lib/schema.json
CHANGED
|
@@ -427,7 +427,7 @@
|
|
|
427
427
|
},
|
|
428
428
|
"required": ["type", "source"],
|
|
429
429
|
"additionalProperties": false,
|
|
430
|
-
"description": "A host-supplied
|
|
430
|
+
"description": "A host-supplied graphic in the band flow. `source` is an expression (never a literal — JSON has no way to write bytes) yielding the complete image file as a `Uint8Array` of PNG, JPEG or SVG bytes; nullish skips the item silently, anything else invalid is a located render error."
|
|
431
431
|
},
|
|
432
432
|
"ImageFit": {
|
|
433
433
|
"type": "string",
|
|
@@ -930,6 +930,10 @@
|
|
|
930
930
|
"description": "Flow each instance's content in this many page columns (integer >= 2). Columned regions cannot nest."
|
|
931
931
|
},
|
|
932
932
|
"sort": { "type": "array", "items": { "$ref": "#/$defs/SortKey" } },
|
|
933
|
+
"instances": {
|
|
934
|
+
"anyOf": [{ "$ref": "#/$defs/Instances" }],
|
|
935
|
+
"description": "Order and cut this group's instances, as the group's own `sort` and `take` order and cut the rows inside each. Absent, every instance renders in first-seen order."
|
|
936
|
+
},
|
|
933
937
|
"take": {
|
|
934
938
|
"anyOf": [{ "$ref": "#/$defs/Count" }],
|
|
935
939
|
"description": "Keep this many rows after this group's `sort`. Integer >= 1. Rows past the count are dropped from aggregates and runners, unlike `visible`."
|
|
@@ -949,6 +953,22 @@
|
|
|
949
953
|
"required": ["name", "by"],
|
|
950
954
|
"additionalProperties": false
|
|
951
955
|
},
|
|
956
|
+
"Instances": {
|
|
957
|
+
"type": "object",
|
|
958
|
+
"properties": {
|
|
959
|
+
"sort": {
|
|
960
|
+
"type": "array",
|
|
961
|
+
"items": { "$ref": "#/$defs/SortKey" },
|
|
962
|
+
"description": "Order the instances. Each key reads the group's own handle once per instance, with `@` and `run` unbound."
|
|
963
|
+
},
|
|
964
|
+
"take": {
|
|
965
|
+
"anyOf": [{ "$ref": "#/$defs/Count" }],
|
|
966
|
+
"description": "Keep this many instances after `sort`. Integer >= 1. A dropped instance never renders, so no runner steps over it, but the enclosing aggregates still count it."
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
"additionalProperties": false,
|
|
970
|
+
"description": "How a group orders and cuts its instances."
|
|
971
|
+
},
|
|
952
972
|
"TableDetail": {
|
|
953
973
|
"type": "object",
|
|
954
974
|
"properties": {
|
package/lib/stream.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* `breathe` sits here because the driver batches on it, not because handing the
|
|
8
8
|
* loop back is a stream rule — the two are independent, and a target breathes
|
|
9
|
-
* while stamping finished pages, which is no walk at all (
|
|
9
|
+
* while stamping finished pages, which is no walk at all (GLOSSARY.md).
|
|
10
10
|
*/
|
|
11
11
|
// The token-join sjabloon renders with — literals verbatim, values through
|
|
12
12
|
// the scalar `display()` rule — re-exported together so event consumers
|
|
@@ -174,7 +174,7 @@ export function styledRuns(tokens) {
|
|
|
174
174
|
return out;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
// The report's own bands, by the role their items wear (
|
|
177
|
+
// The report's own bands, by the role their items wear (GLOSSARY.md, "Band").
|
|
178
178
|
// The page ones are in it although no walk emits them: the question is whose
|
|
179
179
|
// band a role names, not what a walk hands over (ADR 0028).
|
|
180
180
|
let REPORT_BANDS = new Set([
|
package/lib/style.js
CHANGED
|
@@ -33,7 +33,7 @@ let isHex = (value) => typeof value === "string" && HEX.test(value);
|
|
|
33
33
|
// diagnostic at the price of a data file that goes stale (docs/adr/0041).
|
|
34
34
|
let CODE = /^[A-Z]{3}$/;
|
|
35
35
|
/** @type {(value: any) => boolean} */
|
|
36
|
-
let isCode = (value) => typeof value === "string" && CODE.test(value);
|
|
36
|
+
export let isCode = (value) => typeof value === "string" && CODE.test(value);
|
|
37
37
|
// A declaration checks to a message or to null, and `spec` is the sugar for
|
|
38
38
|
// the ordinary case: one predicate, one wording. `format` is the one entry
|
|
39
39
|
// written out longhand, because a value with structure of its own has four
|
|
@@ -96,7 +96,7 @@ let FORMATS = ["number", "currency", "percent", "date"];
|
|
|
96
96
|
// whole rule, and the two halves below -- the check and the resolution -- are
|
|
97
97
|
// the only places it is spelled out.
|
|
98
98
|
|
|
99
|
-
// The four names are Intl's `dateStyle`, and
|
|
99
|
+
// The four names are Intl's `dateStyle`, and GLOSSARY.md's "Form" is the term.
|
|
100
100
|
// A bare `date` means `medium`: the fifth shape Intl's no-option default used
|
|
101
101
|
// to produce was nameable by nothing, so an author held one behaviour more
|
|
102
102
|
// than the vocabulary described (docs/adr/0056).
|
|
@@ -427,7 +427,7 @@ export let isRunName = (name) => RUN.has(name);
|
|
|
427
427
|
export let checkRunStyle = narrowed(RUN_STYLES, "a styled run");
|
|
428
428
|
export let checkSlotStyle = withoutFlow(checkStyle, "a split slot");
|
|
429
429
|
// `valign` is legal exactly where a box has height it did not ask for
|
|
430
|
-
// (
|
|
430
|
+
// (GLOSSARY.md "Box": slack). A slot's box is the split's height, so a slot --
|
|
431
431
|
// text or image -- reads it; a band image's box is exactly its picture's
|
|
432
432
|
// height, so it does not.
|
|
433
433
|
// That is the one name an image accepts in a slot and nowhere else, the same
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quario",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Tiny, CSP-safe report engine for JavaScript. One JSON document renders to HTML, PDF, XLSX, CSV and Word.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"csp",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"sjabloon",
|
|
63
63
|
"padvinder"
|
|
64
64
|
],
|
|
65
|
-
"limit": "
|
|
65
|
+
"limit": "18 kB"
|
|
66
66
|
}
|
|
67
67
|
],
|
|
68
68
|
"engines": {
|