@quario/xlsx 0.1.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 +18 -0
- package/LICENSE +219 -0
- package/README.md +153 -0
- package/lib/cell.js +26 -0
- package/lib/image.js +47 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.js +127 -0
- package/lib/style.js +92 -0
- package/lib/workbook.js +96 -0
- package/package.json +65 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to @quario/xlsx 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.1.0] - 2026-08-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **A workbook from the same compiled report.** Bands become rows; a bare
|
|
15
|
+
interpolation that is a number, boolean, or Date stays that type. The first
|
|
16
|
+
table freezes its header row. exceljs writes the file. Column width
|
|
17
|
+
percentages are unread: a worksheet has no table for a share to be a share
|
|
18
|
+
of.
|
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
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# @quario/xlsx
|
|
2
|
+
|
|
3
|
+
**The spreadsheet render target for [quario](https://www.npmjs.com/package/quario).** Renders a
|
|
4
|
+
report definition to an `.xlsx` workbook with cells typed as a number, boolean, or Date, and a frozen
|
|
5
|
+
header row.
|
|
6
|
+
|
|
7
|
+
Numbers stay numbers, dates stay dates, and booleans stay booleans. Your users get a workbook they
|
|
8
|
+
can keep working in, not a grid of strings.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install quario @quario/xlsx
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
[exceljs](https://github.com/exceljs/exceljs) comes along as a dependency; the engine is a peer, installed beside it. ESM-only,
|
|
17
|
+
Node 22+, and browser-ready through any standards-based ESM bundler. The renderer returns bytes,
|
|
18
|
+
so you decide where they go.
|
|
19
|
+
|
|
20
|
+
> The writer is an implementation detail: exceljs is confined to a single internal module, so it
|
|
21
|
+
> can be swapped behind this exact API without a breaking change.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { writeFileSync } from "node:fs";
|
|
27
|
+
import { xlsx } from "@quario/xlsx";
|
|
28
|
+
import { quario } from "quario";
|
|
29
|
+
|
|
30
|
+
const report = quario().report(schema, funcs);
|
|
31
|
+
const bytes = await report.render(xlsx({ meta: { title: "Sales 2026" } }), data);
|
|
32
|
+
|
|
33
|
+
writeFileSync("sales.xlsx", bytes);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Any schema the other targets render works here unchanged. A spreadsheet has no pages, so page bands
|
|
37
|
+
are ignored.
|
|
38
|
+
|
|
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; `{{ currency(@.amount) }}` becomes text,
|
|
41
|
+
because the formatter already turned the number into a string. Let the spreadsheet do the
|
|
42
|
+
formatting it is good at.
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
### `xlsx(options?)`
|
|
47
|
+
|
|
48
|
+
The target factory: it takes this target's host options and returns the target you pass to
|
|
49
|
+
`render`. `report()` compiles once and `report.render(xlsx(options), data)` resolves the
|
|
50
|
+
workbook bytes. Compile at startup, render per request. Definition problems throw at
|
|
51
|
+
`report()`, at compile time.
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
const report = quario().report(schema, funcs);
|
|
55
|
+
const bytes = await report.render(xlsx(), data);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The compiled report carries `stream` (the raw event generator), `names`, `functions`, and
|
|
59
|
+
`paths`, like every quario report. Engine-level options (`query` budgets, the license key)
|
|
60
|
+
live on the instance, and `q.license` settles with the verification result.
|
|
61
|
+
|
|
62
|
+
Rendering is asynchronous and hands the loop back between batches, so a large report never blocks the
|
|
63
|
+
host. Render-time failures reject with located errors.
|
|
64
|
+
|
|
65
|
+
### Options
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
{
|
|
69
|
+
meta: { title, author, subject }, // optional document properties — strings only, never a date
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Mapping
|
|
74
|
+
|
|
75
|
+
One worksheet, named `Report`, with the banded walk flattened onto it as rows in render order:
|
|
76
|
+
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 row, then report-footer items. Grouping shows up
|
|
78
|
+
as that order alone. Group boundaries add no rows of their own, and `break: "page"` is ignored.
|
|
79
|
+
|
|
80
|
+
Each item takes one row with its cell in the first column, unmerged. Two band roles carry an
|
|
81
|
+
omakase default that the author's own style always overrides: report-header items render bold at
|
|
82
|
+
size 14, group-header items render bold.
|
|
83
|
+
|
|
84
|
+
### Typed cells
|
|
85
|
+
|
|
86
|
+
A cell whose value is one bare interpolation writes its pre-stringify value as a number, boolean, or
|
|
87
|
+
Date. Every other cell (mixed literal and interpolation, a lone null, any non-numeric value) joins
|
|
88
|
+
to display text.
|
|
89
|
+
|
|
90
|
+
**String cells are never formulas.** Cell text beginning with `=`, or anything else, arrives as
|
|
91
|
+
inert literal text: no formula-injection surface, and no apostrophe mangling either.
|
|
92
|
+
|
|
93
|
+
A hidden cell keeps its column slot as an empty cell that retains its style.
|
|
94
|
+
|
|
95
|
+
### Images
|
|
96
|
+
|
|
97
|
+
An image item is not a cell: it takes one worksheet row as its anchor and floats over the sheet as a
|
|
98
|
+
drawing at its natural pixel size. Placement here is best-effort. `fit`, `alt`, and `style` go
|
|
99
|
+
unread, and all of it may change without a breaking change.
|
|
100
|
+
|
|
101
|
+
### Styles
|
|
102
|
+
|
|
103
|
+
| Declaration | Maps to |
|
|
104
|
+
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
105
|
+
| `family` | `sans` → Calibri, `serif` → Times New Roman, `mono` → Courier New; any other name passes through for the host application to resolve |
|
|
106
|
+
| `size`, `bold`, `italic`, `underline`, `strikethrough` | Font (`strikethrough` → writer's `strike`) |
|
|
107
|
+
| `color`, `background` | Font colour, solid fill |
|
|
108
|
+
| `align` | Horizontal alignment |
|
|
109
|
+
|
|
110
|
+
Style blocks layer row under cell, as everywhere in quario.
|
|
111
|
+
|
|
112
|
+
### Freeze
|
|
113
|
+
|
|
114
|
+
The first table's header row anchors the frozen view. Everything from that row up stays visible
|
|
115
|
+
while the body scrolls, the spreadsheet analogue of the PDF target's repeated headers. A report
|
|
116
|
+
without a table freezes nothing. This is normative.
|
|
117
|
+
|
|
118
|
+
### Widths are not read
|
|
119
|
+
|
|
120
|
+
Column `width` percentages size nothing here, and every worksheet column keeps the application
|
|
121
|
+
default. A width is a share of the _table_ width, and a worksheet's columns are global to the sheet.
|
|
122
|
+
Column A carries the flattened band item rows and the table's first column alike, so there is no
|
|
123
|
+
table for a share to be a share of.
|
|
124
|
+
|
|
125
|
+
### Page columns are not read
|
|
126
|
+
|
|
127
|
+
A page column count sizes and splits nothing here, and will not change, as `break: "page"` is
|
|
128
|
+
ignored. Page columns are strips of a page, and this target has no page to strip.
|
|
129
|
+
|
|
130
|
+
## Unlicensed marking
|
|
131
|
+
|
|
132
|
+
An unlicensed render writes the wording from `report-start.marking` twice: as a styled banner in
|
|
133
|
+
row 1 (so the whole report, frozen view included, sits one row lower than a licensed render), and
|
|
134
|
+
as the workbook's description property. Both are normative; banner styling is best-effort. A
|
|
135
|
+
licensed render has no banner and no description marking.
|
|
136
|
+
|
|
137
|
+
## Determinism
|
|
138
|
+
|
|
139
|
+
The document model is deterministic: the workbook's dates are pinned rather than stamped, and
|
|
140
|
+
`meta` is opt-in. The **bytes** are not. The writer stamps archive entries with the packing time.
|
|
141
|
+
Compare extracted content, not digests.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
|
|
146
|
+
licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
147
|
+
|
|
148
|
+
Pass your license key once, on the instance; it is verified offline:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
const q = quario({ license: "quario_..." });
|
|
152
|
+
await q.license; // { licensed: true, licensee: "Acme BV", id: "1-ACME" }
|
|
153
|
+
```
|
package/lib/cell.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* From an event cell to a plain cell descriptor `{ value, format }`, built on
|
|
3
|
+
* the engine's `typed` stream rule. `value` is a native number, boolean, or
|
|
4
|
+
* Date when the cell is one bare interpolation (so numbers stay computable in
|
|
5
|
+
* the sheet), and the display-text join otherwise. `format` is the resolved
|
|
6
|
+
* cell-level formatting from the style module.
|
|
7
|
+
*/
|
|
8
|
+
import { text, typed } from "quario";
|
|
9
|
+
import { format, merge } from "./style.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve one event cell against its enclosing style (a row's style, or an
|
|
13
|
+
* item's band-role default — the layer under the cell's own).
|
|
14
|
+
*
|
|
15
|
+
* @type {(cell: { tokens: any[], style?: any }, under: any) => { value: any, format: any }}
|
|
16
|
+
*/
|
|
17
|
+
let field = (cell, under) => {
|
|
18
|
+
let resolved = merge(under, cell.style);
|
|
19
|
+
let value = typed(cell.tokens);
|
|
20
|
+
return {
|
|
21
|
+
value: value !== undefined ? value : text(cell.tokens),
|
|
22
|
+
format: format(resolved),
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { field };
|
package/lib/image.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An image's own size, read from its header. A floating drawing is placed in
|
|
3
|
+
* pixels, and the event carries the bytes and the format the engine sniffed
|
|
4
|
+
* but no dimensions, so the one thing this target needs to place a picture is
|
|
5
|
+
* worked out here — from the header alone, never by decoding the image.
|
|
6
|
+
*
|
|
7
|
+
* The PDF target reads the same two headers for its own placement, in points.
|
|
8
|
+
* Restated per target rather than shared, like every other coercion at a
|
|
9
|
+
* target's edge: neither package imports the other, and the engine's stream is
|
|
10
|
+
* all they have in common.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @type {(bytes: Uint8Array, at: number) => number} */
|
|
14
|
+
let word = (bytes, at) => (bytes[at] << 8) | bytes[at + 1];
|
|
15
|
+
|
|
16
|
+
/** @type {(bytes: Uint8Array) => { width: number, height: number }} */
|
|
17
|
+
let png = (bytes) => ({ width: word(bytes, 18), height: word(bytes, 22) });
|
|
18
|
+
|
|
19
|
+
// The dimensions live in the frame header, the first SOFn marker: every
|
|
20
|
+
// code in C0..CF except the three in that range that are not frames --
|
|
21
|
+
// DHT, the JPG extension, and DAC.
|
|
22
|
+
/** @type {Record<number, number>} */
|
|
23
|
+
let SKIP = { 0xc4: 1, 0xc8: 1, 0xcc: 1 };
|
|
24
|
+
/** @type {(code: number) => any} */
|
|
25
|
+
let sof = (code) => code >= 0xc0 && code <= 0xcf && !SKIP[code];
|
|
26
|
+
|
|
27
|
+
/** @type {(bytes: Uint8Array) => { width: number, height: number }} */
|
|
28
|
+
let jpeg = (bytes) => {
|
|
29
|
+
for (let at = 2; at + 9 < bytes.length; at += 2 + word(bytes, at + 2)) {
|
|
30
|
+
if (bytes[at] !== 0xff) break;
|
|
31
|
+
if (sof(bytes[at + 1])) return { width: word(bytes, at + 7), height: word(bytes, at + 5) };
|
|
32
|
+
}
|
|
33
|
+
return { width: 0, height: 0 };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The image's size in pixels, or null when the header does not carry one —
|
|
38
|
+
* bytes the engine vouched the magic numbers of, truncated before the size.
|
|
39
|
+
*
|
|
40
|
+
* @param {Uint8Array} bytes The image file.
|
|
41
|
+
* @param {string} format The engine's sniff: `"png"` or `"jpeg"`.
|
|
42
|
+
* @returns {{ width: number, height: number } | null} The size, if readable.
|
|
43
|
+
*/
|
|
44
|
+
export let pixels = (bytes, format) => {
|
|
45
|
+
let size = format === "png" ? png(bytes) : jpeg(bytes);
|
|
46
|
+
return size.width > 0 && size.height > 0 ? size : null;
|
|
47
|
+
};
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Target } from "quario";
|
|
2
|
+
|
|
3
|
+
/** Optional document information; never includes dates, so the document model stays deterministic. */
|
|
4
|
+
export interface XlsxMeta {
|
|
5
|
+
title?: string;
|
|
6
|
+
author?: string;
|
|
7
|
+
subject?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Host controls, taken at the factory call. */
|
|
11
|
+
export interface XlsxOptions {
|
|
12
|
+
meta?: XlsxMeta;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The spreadsheet render target:
|
|
17
|
+
* `quario().report(schema).render(xlsx(), data)` resolves the
|
|
18
|
+
* workbook bytes described in SCHEMA.md ("The XLSX target").
|
|
19
|
+
*/
|
|
20
|
+
export function xlsx(options?: XlsxOptions): Target<"xlsx", Promise<Uint8Array>>;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @quario/xlsx — the spreadsheet render target: a pure consumer of the
|
|
3
|
+
* engine's public event stream, passed to a compiled report's `render`, flattening the
|
|
4
|
+
* banded walk onto one worksheet in render order. Cells that are one bare interpolation keep their native type
|
|
5
|
+
* (numbers stay numbers), the first table
|
|
6
|
+
* anchors the frozen header row, and the closed style
|
|
7
|
+
* vocabulary maps to fonts, fills, and alignment. Column `width` percentages
|
|
8
|
+
* are read by no part of this target: a worksheet's columns are global to the
|
|
9
|
+
* sheet, so there is no table for a share to be a share of. This target is unpaginated,
|
|
10
|
+
* so `report-start.page` band closures and `break: 'page'` are ignored — the
|
|
11
|
+
* same posture as the HTML target.
|
|
12
|
+
*
|
|
13
|
+
* The document model is deterministic — the workbook carries no dates of its
|
|
14
|
+
* own — but the bytes are not: the current writer stamps archive entries with
|
|
15
|
+
* the packing time. Byte-for-byte output is a goal for the writer that will
|
|
16
|
+
* replace `workbook.js`.
|
|
17
|
+
*/
|
|
18
|
+
import { walk } from "quario";
|
|
19
|
+
import { field } from "./cell.js";
|
|
20
|
+
import { pixels } from "./image.js";
|
|
21
|
+
import { append, create, embed, freeze, mark, place, save, sheet } from "./workbook.js";
|
|
22
|
+
|
|
23
|
+
// The options are described once, in the hand-written public declarations, and
|
|
24
|
+
// read back here — a second copy in JSDoc is a copy that drifts.
|
|
25
|
+
/** @import { XlsxOptions } from './index.d.ts' */
|
|
26
|
+
|
|
27
|
+
// Band-role omakase defaults, applied under the author's own style so a
|
|
28
|
+
// declared style always wins. Only the headline roles carry one; every other
|
|
29
|
+
// band writes plain cells. The PDF target carries the same two, byte-identical,
|
|
30
|
+
// in its own `style.js` — SCHEMA.md states the pair for both, so they move
|
|
31
|
+
// together, and a target imports only public engine helpers so there is nowhere
|
|
32
|
+
// to share them from.
|
|
33
|
+
//
|
|
34
|
+
// `@quario/html` deliberately carries none of them: its consumer has a
|
|
35
|
+
// stylesheet and the `q-*` classes are the seam, where a worksheet has neither.
|
|
36
|
+
// The rule is docs/adr/0014-a-target-supplies-defaults-only-where-its-consumer-has-no-seam.md —
|
|
37
|
+
// a target supplies defaults only where its consumer has no seam to supply
|
|
38
|
+
// them. `@quario/html/style.css` and `packages/viewer/lib/style.js` supply them for
|
|
39
|
+
// that target; keep all four in agreement.
|
|
40
|
+
/** @type {Record<string, any>} */
|
|
41
|
+
let ROLES = { "report-header": { bold: true, size: 14 }, "group-header": { bold: true } };
|
|
42
|
+
|
|
43
|
+
// The unlicensed-output marking (LICENSE section 6): one styled banner row
|
|
44
|
+
// above the report, written only on keyless renders through the same cell
|
|
45
|
+
// path as every other row. This is the banner's look; the wording arrives on
|
|
46
|
+
// `report-start`.
|
|
47
|
+
let BANNER = { bold: true, background: "#fff2cc" };
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The spreadsheet render target:
|
|
51
|
+
* `quario().report(schema).render(xlsx(), data)` resolves the
|
|
52
|
+
* complete workbook as a `Uint8Array` (the host writes the file). Rendering
|
|
53
|
+
* runs the engine's `walk` driver over the handlers below, so large reports
|
|
54
|
+
* stay cooperative.
|
|
55
|
+
*
|
|
56
|
+
* @param {XlsxOptions} [options] Host controls (see SCHEMA.md, "The XLSX target").
|
|
57
|
+
* @returns {{name: "xlsx", compile: (stream: any) => (data?: any) => Promise<Uint8Array>}}
|
|
58
|
+
* The target (see SCHEMA.md, "Instances and targets").
|
|
59
|
+
*/
|
|
60
|
+
export function xlsx(options) {
|
|
61
|
+
/** @type {(stream: any) => (data?: any) => Promise<Uint8Array>} */
|
|
62
|
+
let compile = (stream) => async (data) => {
|
|
63
|
+
let workbook = create(options?.meta);
|
|
64
|
+
let worksheet = sheet(workbook);
|
|
65
|
+
// The first table anchors the frozen header row. It is global to the
|
|
66
|
+
// sheet, so later tables never move it.
|
|
67
|
+
let frozen = false;
|
|
68
|
+
// Every image this render has already embedded, keyed by the array the
|
|
69
|
+
// source expression yielded and holding what a second placement of the
|
|
70
|
+
// same bytes needs: the media id and the size read off the header. A logo
|
|
71
|
+
// down a thousand rows is one copy in the file rather than a thousand.
|
|
72
|
+
/** @type {Map<Uint8Array, any>} */
|
|
73
|
+
let images = new Map();
|
|
74
|
+
// A table row and a total row differ only in the style they sit under.
|
|
75
|
+
let record = (/** @type {any} */ event, /** @type {any} */ under) =>
|
|
76
|
+
append(
|
|
77
|
+
worksheet,
|
|
78
|
+
event.cells.map((/** @type {any} */ cell) => field(cell, under)),
|
|
79
|
+
);
|
|
80
|
+
await walk(stream(data), {
|
|
81
|
+
"report-start": (event) => {
|
|
82
|
+
if (event.marking) {
|
|
83
|
+
mark(workbook, event.marking);
|
|
84
|
+
append(worksheet, [field({ tokens: [{ literal: event.marking }], style: BANNER }, null)]);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
item: (event) => {
|
|
88
|
+
let role = Object.hasOwn(ROLES, event.role) ? ROLES[event.role] : null;
|
|
89
|
+
append(worksheet, [field({ tokens: event.tokens, style: event.style }, role)]);
|
|
90
|
+
},
|
|
91
|
+
// An image is not a cell: it takes one row as its anchor and floats
|
|
92
|
+
// over the sheet from there, at its own pixel size. `fit` is not read
|
|
93
|
+
// -- a worksheet has no content width for a share to be a share of,
|
|
94
|
+
// the same ground the column widths go unread on (ADR 0008) -- and
|
|
95
|
+
// neither is `alt` or `style`. All of this is best-effort and may
|
|
96
|
+
// change (SCHEMA.md, "The XLSX target").
|
|
97
|
+
image: (event) => {
|
|
98
|
+
let known = images.get(event.bytes);
|
|
99
|
+
if (!known) {
|
|
100
|
+
let size = pixels(event.bytes, event.format);
|
|
101
|
+
// The engine vouched for the magic numbers, not for the rest of the
|
|
102
|
+
// file. A header too short to carry a size leaves nothing to place a
|
|
103
|
+
// drawing at, and failing beats dropping the picture in silence
|
|
104
|
+
// (SCHEMA.md, "Image item").
|
|
105
|
+
if (!size) throw Error("image: could not read the image's size from its bytes");
|
|
106
|
+
known = { ...size, id: embed(workbook, event.bytes, event.format) };
|
|
107
|
+
images.set(event.bytes, known);
|
|
108
|
+
}
|
|
109
|
+
// The anchor: a row of its own, with nothing written in it, which the
|
|
110
|
+
// picture then floats over.
|
|
111
|
+
place(worksheet, known.id, known, append(worksheet, []));
|
|
112
|
+
},
|
|
113
|
+
"table-start": (event) => {
|
|
114
|
+
let headers = event.columns.map((/** @type {any} */ column) => field(column.header, null));
|
|
115
|
+
let row = append(worksheet, headers);
|
|
116
|
+
if (!frozen) {
|
|
117
|
+
freeze(worksheet, row);
|
|
118
|
+
frozen = true;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
row: (event) => record(event, event.style),
|
|
122
|
+
"total-row": (event) => record(event, null),
|
|
123
|
+
});
|
|
124
|
+
return save(workbook);
|
|
125
|
+
};
|
|
126
|
+
return { name: "xlsx", compile };
|
|
127
|
+
}
|
package/lib/style.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The XLSX target's reading of the closed style vocabulary — the counterpart
|
|
3
|
+
* of the CSS table in the HTML target and the points-and-colours module in
|
|
4
|
+
* the PDF target. Declarations are lenient at render: a value of the wrong
|
|
5
|
+
* shape contributes nothing rather than reaching the sheet. Everything
|
|
6
|
+
* produced here is plain data shaped for the workbook writer; nothing in this
|
|
7
|
+
* module touches the writer itself.
|
|
8
|
+
*
|
|
9
|
+
* `finite`/`HEX` are restated per target on purpose: the coercions are each
|
|
10
|
+
* target's own edge, never shared engine code.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @type {(value: any) => boolean} */
|
|
14
|
+
let finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
15
|
+
let HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
16
|
+
|
|
17
|
+
// The generic families as the fonts spreadsheet apps ship with; any other
|
|
18
|
+
// name passes through verbatim for the host application to resolve.
|
|
19
|
+
/** @type {Record<string, string>} */
|
|
20
|
+
let FAMILY = { sans: "Calibri", serif: "Times New Roman", mono: "Courier New" };
|
|
21
|
+
let ALIGNMENTS = ["left", "center", "right"];
|
|
22
|
+
|
|
23
|
+
// A declared colour as an ARGB string, or null when the value is not one.
|
|
24
|
+
/** @type {(value: any) => string | null} */
|
|
25
|
+
let argb = (value) => {
|
|
26
|
+
let match = typeof value === "string" && HEX.exec(value);
|
|
27
|
+
if (!match) return null;
|
|
28
|
+
// `#abc` is `#aabbcc`.
|
|
29
|
+
let digits = match[1].length === 3 ? match[1].replace(/./g, (d) => d + d) : match[1];
|
|
30
|
+
return "FF" + digits.toUpperCase();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Style blocks layer outward-in: row under cell.
|
|
34
|
+
/** @type {(under: any, over: any) => any} */
|
|
35
|
+
let merge = (under, over) => (under ? (over ? { ...under, ...over } : under) : over || {});
|
|
36
|
+
|
|
37
|
+
/** @type {(out: any, key: string, value: any) => any} */
|
|
38
|
+
let put = (out, key, value) => {
|
|
39
|
+
if (!value) return out;
|
|
40
|
+
(out ??= {})[key] = value;
|
|
41
|
+
return out;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** @type {(family: any) => string} */
|
|
45
|
+
let face = (family) => {
|
|
46
|
+
if (typeof family !== "string" || !family) return "";
|
|
47
|
+
let key = family.toLowerCase();
|
|
48
|
+
// Own-key lookup: `constructor` must not resolve an inherited member.
|
|
49
|
+
return Object.hasOwn(FAMILY, key) ? FAMILY[key] : family;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** @type {(value: any) => number | null} */
|
|
53
|
+
let size = (value) => (finite(value) && value > 0 ? value : null);
|
|
54
|
+
/** @type {(value: any) => true | null} */
|
|
55
|
+
let flag = (value) => (value ? true : null);
|
|
56
|
+
/** @type {(value: any) => { argb: string } | null} */
|
|
57
|
+
let tint = (value) => {
|
|
58
|
+
let color = argb(value);
|
|
59
|
+
return color ? { argb: color } : null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// The font half of a resolved style, or null when the style declares none
|
|
63
|
+
// of it.
|
|
64
|
+
/** @type {(style: any) => any} */
|
|
65
|
+
let font = (style) => {
|
|
66
|
+
let out = put(null, "name", face(style.family));
|
|
67
|
+
out = put(out, "size", size(style.size));
|
|
68
|
+
out = put(out, "bold", flag(style.bold));
|
|
69
|
+
out = put(out, "italic", flag(style.italic));
|
|
70
|
+
out = put(out, "underline", flag(style.underline));
|
|
71
|
+
// exceljs names the flag `strike`; the authoring surface says strikethrough.
|
|
72
|
+
out = put(out, "strike", flag(style.strikethrough));
|
|
73
|
+
return put(out, "color", tint(style.color));
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** @type {(value: any) => any} */
|
|
77
|
+
let fill = (value) => {
|
|
78
|
+
let color = argb(value);
|
|
79
|
+
return color ? { type: "pattern", pattern: "solid", fgColor: { argb: color } } : null;
|
|
80
|
+
};
|
|
81
|
+
/** @type {(value: any) => any} */
|
|
82
|
+
let align = (value) => (ALIGNMENTS.includes(value) ? { horizontal: value } : null);
|
|
83
|
+
|
|
84
|
+
// The whole-cell reading: the font plus the parts only a cell can carry.
|
|
85
|
+
/** @type {(style: any) => any} */
|
|
86
|
+
let format = (style) => {
|
|
87
|
+
let out = put(null, "font", font(style));
|
|
88
|
+
out = put(out, "fill", fill(style.background));
|
|
89
|
+
return put(out, "alignment", align(style.align));
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export { format, merge };
|
package/lib/workbook.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workbook writer — the only module that talks to exceljs, so replacing
|
|
3
|
+
* the writer later means replacing this file and nothing else. It consumes
|
|
4
|
+
* plain cell descriptors and style objects; every other module in the package
|
|
5
|
+
* emits data, never writer calls.
|
|
6
|
+
*
|
|
7
|
+
* The default import is deliberate: the writer is CommonJS and its named
|
|
8
|
+
* exports are not visible to Node's ESM loader.
|
|
9
|
+
*/
|
|
10
|
+
import ExcelJS from "exceljs";
|
|
11
|
+
|
|
12
|
+
// Host metadata keys as the workbook properties they become.
|
|
13
|
+
/** @type {Record<string, string>} */
|
|
14
|
+
let META = { title: "title", author: "creator", subject: "subject" };
|
|
15
|
+
/** @type {(workbook: any, meta: any) => any} */
|
|
16
|
+
let describe = (workbook, meta) => {
|
|
17
|
+
if (!meta) return workbook;
|
|
18
|
+
for (let from in META) if (typeof meta[from] === "string") workbook[META[from]] = meta[from];
|
|
19
|
+
return workbook;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// A fresh workbook. Never the current time: a timestamp would make the same
|
|
23
|
+
// input produce a different document model on every run.
|
|
24
|
+
/** @type {(meta: any) => any} */
|
|
25
|
+
let create = (meta) => {
|
|
26
|
+
let workbook = new ExcelJS.Workbook();
|
|
27
|
+
workbook.created = new Date(0);
|
|
28
|
+
workbook.modified = new Date(0);
|
|
29
|
+
return describe(workbook, meta);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** @type {(workbook: any) => any} */
|
|
33
|
+
let sheet = (workbook) => workbook.addWorksheet("Report");
|
|
34
|
+
|
|
35
|
+
let KEYS = ["font", "fill", "alignment"];
|
|
36
|
+
/** @type {(cell: any, format: any) => void} */
|
|
37
|
+
let paint = (cell, format) => {
|
|
38
|
+
for (let key of KEYS) if (format[key]) cell[key] = format[key];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Write one worksheet row from cell descriptors; returns the row number.
|
|
42
|
+
/** @type {(worksheet: any, fields: { value: any, format: any }[]) => number} */
|
|
43
|
+
let append = (worksheet, fields) => {
|
|
44
|
+
let row = worksheet.addRow(fields.map((field) => field.value));
|
|
45
|
+
for (let index = 0; index < fields.length; index++) {
|
|
46
|
+
let field = fields[index];
|
|
47
|
+
if (field.format) paint(row.getCell(index + 1), field.format);
|
|
48
|
+
}
|
|
49
|
+
return row.number;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// One image in the workbook's media, as the engine's sniffed format calls it.
|
|
53
|
+
// The id comes back so a caller that places the same bytes twice can embed
|
|
54
|
+
// them once: this writer keeps no such tally, and adding the same buffer twice
|
|
55
|
+
// writes two copies into the file.
|
|
56
|
+
/** @type {(workbook: any, bytes: Uint8Array, format: string) => number} */
|
|
57
|
+
let embed = (workbook, bytes, format) => workbook.addImage({ buffer: bytes, extension: format });
|
|
58
|
+
|
|
59
|
+
// One picture, floating over the row it is anchored to. Wholly best-effort
|
|
60
|
+
// placement (SCHEMA.md): the drawing hangs from the top-left of that row at
|
|
61
|
+
// the size it was measured at, over whatever follows it down the sheet.
|
|
62
|
+
/** @type {(worksheet: any, id: number, size: any, row: number) => void} */
|
|
63
|
+
let place = (worksheet, id, size, row) => {
|
|
64
|
+
worksheet.addImage(id, {
|
|
65
|
+
// exceljs counts anchors from zero, while a row number starts at one.
|
|
66
|
+
tl: { col: 0, row: row - 1 },
|
|
67
|
+
ext: { width: size.width, height: size.height },
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Freeze everything above the body so the header row stays visible.
|
|
72
|
+
/** @type {(worksheet: any, row: number) => void} */
|
|
73
|
+
let freeze = (worksheet, row) => {
|
|
74
|
+
worksheet.views = [{ state: "frozen", ySplit: row }];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// The unlicensed-output marking on the document itself (LICENSE section 6):
|
|
78
|
+
// the workbook description carries the engine's wording wherever file
|
|
79
|
+
// properties show.
|
|
80
|
+
/** @type {(workbook: any, text: string) => void} */
|
|
81
|
+
let mark = (workbook, text) => {
|
|
82
|
+
workbook.description = text;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// The finished document. `writeBuffer()` resolves a Node Buffer or, in the
|
|
86
|
+
// browser build, a bundled polyfill over an ArrayBuffer — both normalize to
|
|
87
|
+
// a plain view without copying.
|
|
88
|
+
/** @type {(workbook: any) => Promise<Uint8Array>} */
|
|
89
|
+
let save = async (workbook) => {
|
|
90
|
+
let out = await workbook.xlsx.writeBuffer();
|
|
91
|
+
return out instanceof Uint8Array
|
|
92
|
+
? new Uint8Array(out.buffer, out.byteOffset, out.byteLength)
|
|
93
|
+
: new Uint8Array(out);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export { append, create, embed, freeze, mark, place, save, sheet };
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quario/xlsx",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The spreadsheet render target for quario — in the makings, not yet released",
|
|
5
|
+
"homepage": "https://getquario.com",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/getquario/quario.git",
|
|
10
|
+
"directory": "packages/xlsx"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"CHANGELOG.md",
|
|
14
|
+
"lib"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"types": "lib/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./lib/index.d.ts",
|
|
21
|
+
"default": "./lib/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"check": "npm run size && npm test && npm run test:browser",
|
|
30
|
+
"size": "size-limit",
|
|
31
|
+
"test": "npm run test:unit && npm run test:types",
|
|
32
|
+
"test:browser": "node test/browser/setup.js",
|
|
33
|
+
"test:types": "tsc && attw --pack . --profile esm-only",
|
|
34
|
+
"test:unit": "node --disallow-code-generation-from-strings --test --test-concurrency=1 test/*.test.js",
|
|
35
|
+
"prepack": "node -e \"require('fs').copyFileSync('../../LICENSE','LICENSE')\"",
|
|
36
|
+
"postpack": "node -e \"require('fs').rmSync('LICENSE',{force:true})\""
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"exceljs": "^4.4.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@arethetypeswrong/cli": "^0.18.3",
|
|
43
|
+
"@size-limit/preset-small-lib": "^13.0.3",
|
|
44
|
+
"@types/node": "^22.20.1",
|
|
45
|
+
"quario": "^0.1.0",
|
|
46
|
+
"size-limit": "^13.0.3",
|
|
47
|
+
"typescript": "^7.0.2"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"quario": "^0.1.0"
|
|
51
|
+
},
|
|
52
|
+
"size-limit": [
|
|
53
|
+
{
|
|
54
|
+
"path": "lib/index.js",
|
|
55
|
+
"ignore": [
|
|
56
|
+
"quario",
|
|
57
|
+
"exceljs"
|
|
58
|
+
],
|
|
59
|
+
"limit": "2 kB"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=22.0.0"
|
|
64
|
+
}
|
|
65
|
+
}
|