@quario/xlsx 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +30 -29
- package/lib/index.d.ts +6 -1
- package/lib/index.js +10 -2
- package/lib/workbook.js +8 -4
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# @quario/xlsx
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **Every target factory now refuses an option it does not understand.** An unknown key, a key with
|
|
8
|
+
a value of the wrong type, or an `options` that is not an object throws a `TypeError` at the
|
|
9
|
+
factory call, naming the option path — `options.meta.title`, `options.page.size`. `@quario/docx`
|
|
10
|
+
already behaved this way. `@quario/pdf`, `@quario/html`, `@quario/xlsx` and `@quario/layout` read
|
|
11
|
+
the keys they knew and ignored the rest, so a typo cost you the option you meant to set with no
|
|
12
|
+
signal at any point. `csv()` takes no options, and it refuses an argument rather than discarding
|
|
13
|
+
one.
|
|
14
|
+
|
|
15
|
+
The check itself is one thing, so the engine now exports it: `hostOptions` closes a key set and
|
|
16
|
+
`hostMeta` validates the `{ title, author, subject }` contract the document targets share. A
|
|
17
|
+
fourth document property is one edit rather than three.
|
|
18
|
+
|
|
19
|
+
**What this changes for you.** One options object spread across several targets stops working if
|
|
20
|
+
any target does not know one of its keys — `const o = { page, fonts, meta }` passed to `pdf(o)`,
|
|
21
|
+
`html(o)` and `xlsx(o)` is three different key sets. An object holding only what its consumers
|
|
22
|
+
share is unaffected, so `{ page, fonts }` into both `pdf()` and `layout()` still works.
|
|
23
|
+
TypeScript does not warn about this: excess-property checking fires on an object literal and not
|
|
24
|
+
on a variable, so a bag held in a `const` compiles clean and throws when you call the factory.
|
|
25
|
+
|
|
26
|
+
Nullish is absence everywhere, at both levels, so `{ meta: config.meta ?? null }` and
|
|
27
|
+
`{ meta: { title: config.title } }` over a config that carries neither are both fine.
|
|
28
|
+
`html({ paths })` now takes `true` or `false` rather than anything truthy, so `paths: "no"` throws
|
|
29
|
+
instead of turning path stamping on. A `fonts` mapping given as an array is refused by
|
|
30
|
+
`@quario/html` and `@quario/layout` rather than read as families named `0`, `1`, `2`.
|
|
31
|
+
|
|
32
|
+
Every host-option failure is now a `TypeError` rather than a plain `Error`. A `catch` that tests
|
|
33
|
+
`instanceof Error` is unaffected. One that compares the constructor is not.
|
|
34
|
+
|
|
35
|
+
`@quario/docx` is relaxed in three places, each of them now accepting what it refused: `meta: null`
|
|
36
|
+
and `page: null` are absence rather than errors, a property written as `meta: { title: undefined }`
|
|
37
|
+
is a property you did not write rather than one of the wrong type, and an inherited enumerable key
|
|
38
|
+
is no longer reported as an option you wrote.
|
|
39
|
+
|
|
40
|
+
`@quario/pdf`, `@quario/xlsx` and `@quario/docx` now copy `meta` at the factory call. Mutating the
|
|
41
|
+
object you passed no longer changes what a configured target writes.
|
|
42
|
+
|
|
43
|
+
- **`meta` is now read once, at the factory call.** It was read inside the render, so a host that
|
|
44
|
+
mutated its options object between renders got a different workbook from the same configured
|
|
45
|
+
target. The documented behaviour was always the factory call. If you were mutating an options
|
|
46
|
+
object to change the document properties, build a new target instead.
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- Updated dependencies
|
|
51
|
+
- quario@0.9.0
|
|
52
|
+
|
|
3
53
|
## 0.7.1
|
|
4
54
|
|
|
5
55
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -13,12 +13,12 @@ can keep working in, not a grid of strings.
|
|
|
13
13
|
npm install quario @quario/xlsx
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
[exceljs](https://github.com/exceljs/exceljs)
|
|
16
|
+
[exceljs](https://github.com/exceljs/exceljs) arrives as a dependency. The engine is a peer, installed beside it. ESM-only,
|
|
17
17
|
Node 22+, and browser-ready through any standards-based ESM bundler. The renderer returns bytes,
|
|
18
18
|
so you decide where they go.
|
|
19
19
|
|
|
20
|
-
> The writer is an implementation detail
|
|
21
|
-
> can
|
|
20
|
+
> The writer is an implementation detail. This package confines exceljs to a single internal
|
|
21
|
+
> module, so a later version can swap it behind this exact API without a breaking change.
|
|
22
22
|
|
|
23
23
|
## Quick start
|
|
24
24
|
|
|
@@ -33,11 +33,11 @@ const bytes = await report.render(xlsx({ meta: { title: "Sales 2026" } }), data)
|
|
|
33
33
|
writeFileSync("sales.xlsx", bytes);
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Any schema the other targets render works here unchanged. A spreadsheet has no pages, so
|
|
37
|
-
|
|
36
|
+
Any schema the other targets render works here unchanged. A spreadsheet has no pages, so this
|
|
37
|
+
target ignores page bands.
|
|
38
38
|
|
|
39
39
|
One tip that changes the output a lot: **interpolate the bare value, not a formatter.** A cell
|
|
40
|
-
written `{{ @.amount }}` becomes a real numeric cell
|
|
40
|
+
written `{{ @.amount }}` becomes a real numeric cell. `{{ currency(@.amount) }}` becomes text,
|
|
41
41
|
because the formatter already turned the number into a string. Let the spreadsheet do the
|
|
42
42
|
formatting it is good at.
|
|
43
43
|
|
|
@@ -45,8 +45,9 @@ formatting it is good at.
|
|
|
45
45
|
|
|
46
46
|
### `xlsx(options?)`
|
|
47
47
|
|
|
48
|
-
The target factory
|
|
49
|
-
`render`. `
|
|
48
|
+
The target factory takes this target's host options, validates them at the call, and returns the
|
|
49
|
+
target you pass to `render`. The factory refuses an option it does not know. It throws a `TypeError` at the call for an unknown key. It throws one also for a key with a value of the wrong type. It also reads `meta` at the call. One configured target
|
|
50
|
+
therefore always writes the same document. `report()` compiles once and `report.render(xlsx(options), data)` resolves the
|
|
50
51
|
workbook bytes. Compile at startup, render per request. Definition problems throw at
|
|
51
52
|
`report()`, at compile time.
|
|
52
53
|
|
|
@@ -59,7 +60,7 @@ The compiled report carries `stream` (the raw event generator), `names`, `functi
|
|
|
59
60
|
`paths`, like every quario report. Engine-level options (`query` budgets, the license key)
|
|
60
61
|
live on the instance, and `q.license` settles with the verification result.
|
|
61
62
|
|
|
62
|
-
Rendering is asynchronous and
|
|
63
|
+
Rendering is asynchronous and returns the loop between batches, so a large report never blocks the
|
|
63
64
|
host. Render-time failures reject with located errors.
|
|
64
65
|
|
|
65
66
|
### Options
|
|
@@ -74,12 +75,12 @@ host. Render-time failures reject with located errors.
|
|
|
74
75
|
|
|
75
76
|
One worksheet, named `Report`, with the banded walk flattened onto it as rows in render order:
|
|
76
77
|
report-header items, then per group instance its header items, nested content, and footer items,
|
|
77
|
-
then the table's header row, data rows, and total rows, then report-footer items. Grouping
|
|
78
|
-
as that order alone. Group boundaries add no rows of their own
|
|
79
|
-
`reset: "page"
|
|
78
|
+
then the table's header row, data rows, and total rows, then report-footer items. Grouping exists
|
|
79
|
+
as that order alone. Group boundaries add no rows of their own. This target ignores `break: "page"`,
|
|
80
|
+
`reset: "page"`, `page.margin`, and a report header's `height`.
|
|
80
81
|
|
|
81
|
-
Each item takes one row with its cell in the first column, unmerged
|
|
82
|
-
slots as the cells across it (slot widths withdrawn, a slot rendering nothing an empty cell)
|
|
82
|
+
Each item takes one row with its cell in the first column, unmerged. A split takes one row with its
|
|
83
|
+
slots as the cells across it (slot widths withdrawn, a slot rendering nothing an empty cell). A
|
|
83
84
|
spanning header or total cell is a merged range. Two band roles carry an
|
|
84
85
|
omakase default that the author's own style always overrides: report-header items render bold at
|
|
85
86
|
size 14, group-header items render bold.
|
|
@@ -99,9 +100,9 @@ A hidden cell keeps its column slot as an empty cell that retains its style.
|
|
|
99
100
|
|
|
100
101
|
`format` writes a number format so the grid presents what the other targets do. `date` builds
|
|
101
102
|
its pattern from the cell's `form` (`dd mmm yyyy` undeclared), with month and weekday names
|
|
102
|
-
spelled by the reader's application
|
|
103
|
-
fraction-digit count
|
|
104
|
-
`{ "kind": "number", "digits": 3 }` is `#,##0.000
|
|
103
|
+
spelled by the reader's application. The number kinds build theirs from the declaration's
|
|
104
|
+
fraction-digit count: an undeclared `number` is `#,##0.00`, `percent` is `0.00%`, and
|
|
105
|
+
`{ "kind": "number", "digits": 3 }` is `#,##0.000`. `currency` is the code the cell wears
|
|
105
106
|
— its own `currency` when it declares one, else the instance's — over that currency's minor
|
|
106
107
|
units unless `digits` says otherwise: `"USD"#,##0.00`, `"JPY"#,##0`, `"BHD"#,##0.000`. A kind
|
|
107
108
|
on the wrong type, or a code the engine cannot read, writes no format.
|
|
@@ -119,18 +120,18 @@ every target alike.
|
|
|
119
120
|
|
|
120
121
|
| Declaration | Maps to |
|
|
121
122
|
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
122
|
-
| `family` | `sans` → Calibri, `serif` → Times New Roman, `mono` → Courier New
|
|
123
|
+
| `family` | `sans` → Calibri, `serif` → Times New Roman, `mono` → Courier New. Any other name passes through for the host application to resolve |
|
|
123
124
|
| `size`, `bold`, `italic`, `underline`, `strikethrough` | Font (`strikethrough` → writer's `strike`) |
|
|
124
125
|
| `color`, `background` | Font colour, solid fill |
|
|
125
126
|
| `align` | Horizontal alignment |
|
|
126
|
-
| `valign` | Vertical alignment
|
|
127
|
-
| `border*` | Cell borders (`solid` → `thin`, `dashed`, `dotted
|
|
127
|
+
| `valign` | Vertical alignment. Undeclared writes nothing, so the spreadsheet application keeps its own default |
|
|
128
|
+
| `border*` | Cell borders (`solid` → `thin`, `dashed`, `dotted`, with width approximated). A row's borders arrive on its cells already |
|
|
128
129
|
| `format`, `currency` | A number format — see [Number formats](#number-formats) |
|
|
129
130
|
| `uppercase`, `padding*`, `spaceBefore`, `spaceAfter` | Not read: a grid has no text-transform, no cell inset of its own, and no flow |
|
|
130
131
|
|
|
131
132
|
Style blocks layer row under cell, as everywhere in quario, with the report default outermost.
|
|
132
|
-
|
|
133
|
-
contains a newline wraps.
|
|
133
|
+
This target writes every cell at 10 points unless something declares otherwise, and a cell whose
|
|
134
|
+
display contains a newline wraps.
|
|
134
135
|
|
|
135
136
|
### Freeze
|
|
136
137
|
|
|
@@ -148,20 +149,20 @@ table for a share to be a share of.
|
|
|
148
149
|
### Page columns are withdrawn
|
|
149
150
|
|
|
150
151
|
A [page column](https://getquario.com/docs/reference/support-matrix/#page-and-pagination) count
|
|
151
|
-
sizes and splits nothing here, and will not change, as `break: "page"
|
|
152
|
+
sizes and splits nothing here, and will not change, as this target ignores `break: "page"`. Page columns
|
|
152
153
|
are strips of a page, and this target has no page to strip.
|
|
153
154
|
|
|
154
155
|
## Unlicensed marking
|
|
155
156
|
|
|
156
157
|
An unlicensed render writes the wording from `report-start.marking` twice: as a styled banner in
|
|
157
158
|
row 1 (so the whole report, frozen view included, sits one row lower than a licensed render), and
|
|
158
|
-
as the workbook's description property. Both are normative
|
|
159
|
+
as the workbook's description property. Both are normative. Banner styling is best-effort. A
|
|
159
160
|
licensed render has no banner and no description marking.
|
|
160
161
|
|
|
161
162
|
## Determinism
|
|
162
163
|
|
|
163
|
-
The document model is deterministic
|
|
164
|
-
`meta` is opt-in. The **bytes** are not. The writer stamps archive entries with the packing time.
|
|
164
|
+
The document model is deterministic. This target pins the workbook's dates rather than stamping
|
|
165
|
+
them, and `meta` is opt-in. The **bytes** are not. The writer stamps archive entries with the packing time.
|
|
165
166
|
Compare extracted content, not digests.
|
|
166
167
|
|
|
167
168
|
The full contract is [The XLSX target](https://getquario.com/docs/diving-deeper/xlsx-target/),
|
|
@@ -177,10 +178,10 @@ specification of what a report may declare, and
|
|
|
177
178
|
|
|
178
179
|
## License
|
|
179
180
|
|
|
180
|
-
Commercial software with readable source.
|
|
181
|
-
licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
181
|
+
Commercial software with readable source. Evaluation is free, unlimited, and watermarked.
|
|
182
|
+
Per-developer licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
182
183
|
|
|
183
|
-
Pass your license key once, on the instance
|
|
184
|
+
Pass your license key once, on the instance. quario verifies it offline:
|
|
184
185
|
|
|
185
186
|
```js
|
|
186
187
|
const q = quario({ license: "quario_..." });
|
package/lib/index.d.ts
CHANGED
|
@@ -7,7 +7,12 @@ export interface XlsxMeta {
|
|
|
7
7
|
subject?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Host controls, taken and validated at the factory call: an option this
|
|
12
|
+
* target does not understand, or one of the wrong type, throws a `TypeError`
|
|
13
|
+
* there rather than costing the host the option in silence. `meta` is read
|
|
14
|
+
* there too, so one configured target always writes the same document.
|
|
15
|
+
*/
|
|
11
16
|
export interface XlsxOptions {
|
|
12
17
|
meta?: XlsxMeta;
|
|
13
18
|
}
|
package/lib/index.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* the packing time. Byte-for-byte output is a goal for the writer that will
|
|
16
16
|
* replace `workbook.js`.
|
|
17
17
|
*/
|
|
18
|
-
import { walk } from "quario";
|
|
18
|
+
import { hostMeta, hostOptions, walk } from "quario";
|
|
19
19
|
import { field } from "./cell.js";
|
|
20
20
|
import { merge as under } from "./style.js";
|
|
21
21
|
import { append, create, embed, freeze, mark, place, save, sheet, span } from "./workbook.js";
|
|
@@ -66,14 +66,22 @@ let BANNER = { bold: true, background: "#fff2cc" };
|
|
|
66
66
|
* runs the engine's `walk` driver over the handlers below, so large reports
|
|
67
67
|
* stay cooperative.
|
|
68
68
|
*
|
|
69
|
+
* Host controls are taken and validated here, at the factory call: an option
|
|
70
|
+
* this target does not understand, or one of the wrong type, throws where the
|
|
71
|
+
* host wrote it rather than costing them the option in silence
|
|
72
|
+
* (`docs/adr/0072`). `meta` is also *read* here, so a render is reproducible
|
|
73
|
+
* from the call that configured it.
|
|
74
|
+
*
|
|
69
75
|
* @param {XlsxOptions} [options] Host controls (see SCHEMA.md, "The XLSX target").
|
|
70
76
|
* @returns {{name: "xlsx", compile: (stream: any) => (data?: any) => Promise<Uint8Array>}}
|
|
71
77
|
* The target (see SCHEMA.md, "Instances and targets").
|
|
72
78
|
*/
|
|
73
79
|
export function xlsx(options) {
|
|
80
|
+
hostOptions(options, ["meta"], "options");
|
|
81
|
+
let meta = hostMeta(options?.meta, "options.meta");
|
|
74
82
|
/** @type {(stream: any) => (data?: any) => Promise<Uint8Array>} */
|
|
75
83
|
let compile = (stream) => async (data) => {
|
|
76
|
-
let workbook = create(
|
|
84
|
+
let workbook = create(meta);
|
|
77
85
|
let worksheet = sheet(workbook);
|
|
78
86
|
// The first table anchors the frozen header row. It is global to the
|
|
79
87
|
// sheet, so later tables never move it.
|
package/lib/workbook.js
CHANGED
|
@@ -10,12 +10,16 @@
|
|
|
10
10
|
import ExcelJS from "exceljs";
|
|
11
11
|
|
|
12
12
|
// Host metadata keys as the workbook properties they become -- `author` is the
|
|
13
|
-
// only one exceljs names differently
|
|
13
|
+
// only one exceljs names differently, and this mapping is all this target owns
|
|
14
|
+
// of `meta`. The contract itself (which properties, each a string, never a
|
|
15
|
+
// date) is the engine's `hostMeta`, checked at the factory call, so nothing
|
|
16
|
+
// here restates it: a present property is a string, and a nullish one is a
|
|
17
|
+
// property the host did not write.
|
|
14
18
|
/** @type {(workbook: any, meta: any) => void} */
|
|
15
19
|
let describe = (workbook, meta) => {
|
|
16
|
-
if (
|
|
17
|
-
if (
|
|
18
|
-
if (
|
|
20
|
+
if (meta.title != null) workbook.title = meta.title;
|
|
21
|
+
if (meta.author != null) workbook.creator = meta.author;
|
|
22
|
+
if (meta.subject != null) workbook.subject = meta.subject;
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
// A fresh workbook. Never the current time: a timestamp would make the same
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quario/xlsx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The spreadsheet render target for quario — in the makings, not yet released",
|
|
5
5
|
"homepage": "https://getquario.com",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"@arethetypeswrong/cli": "^0.18.3",
|
|
43
43
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
44
44
|
"@types/node": "^22.20.1",
|
|
45
|
-
"quario": "^0.
|
|
45
|
+
"quario": "^0.9.0",
|
|
46
46
|
"size-limit": "^13.0.3",
|
|
47
47
|
"typescript": "^7.0.2"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"quario": "^0.
|
|
50
|
+
"quario": "^0.9.0"
|
|
51
51
|
},
|
|
52
52
|
"size-limit": [
|
|
53
53
|
{
|