@quario/csv 0.7.0 → 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 CHANGED
@@ -1,65 +1,116 @@
1
- # Changelog
1
+ # @quario/csv
2
2
 
3
- All notable changes to @quario/csv are documented in this file.
3
+ ## 0.8.0
4
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).
5
+ ### Minor Changes
7
6
 
8
- ## [Unreleased]
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.
9
14
 
10
- ## [0.7.0] - 2026-09-07
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.
11
18
 
12
- ### Changed
19
+ **What this changes for you.** One options object spread across several targets stops working if
20
+ any target does not know one of its keys — `const o = { page, fonts, meta }` passed to `pdf(o)`,
21
+ `html(o)` and `xlsx(o)` is three different key sets. An object holding only what its consumers
22
+ share is unaffected, so `{ page, fonts }` into both `pdf()` and `layout()` still works.
23
+ TypeScript does not warn about this: excess-property checking fires on an object literal and not
24
+ on a variable, so a bag held in a `const` compiles clean and throws when you call the factory.
25
+
26
+ Nullish is absence everywhere, at both levels, so `{ meta: config.meta ?? null }` and
27
+ `{ meta: { title: config.title } }` over a config that carries neither are both fine.
28
+ `html({ paths })` now takes `true` or `false` rather than anything truthy, so `paths: "no"` throws
29
+ instead of turning path stamping on. A `fonts` mapping given as an array is refused by
30
+ `@quario/html` and `@quario/layout` rather than read as families named `0`, `1`, `2`.
31
+
32
+ Every host-option failure is now a `TypeError` rather than a plain `Error`. A `catch` that tests
33
+ `instanceof Error` is unaffected. One that compares the constructor is not.
34
+
35
+ `@quario/docx` is relaxed in three places, each of them now accepting what it refused: `meta: null`
36
+ and `page: null` are absence rather than errors, a property written as `meta: { title: undefined }`
37
+ is a property you did not write rather than one of the wrong type, and an inherited enumerable key
38
+ is no longer reported as an option you wrote.
39
+
40
+ `@quario/pdf`, `@quario/xlsx` and `@quario/docx` now copy `meta` at the factory call. Mutating the
41
+ object you passed no longer changes what a configured target writes.
42
+
43
+ ### Patch Changes
44
+
45
+ - Updated dependencies
46
+ - quario@0.9.0
47
+
48
+ ## 0.7.1
49
+
50
+ ### Patch Changes
51
+
52
+ - **Every published README says where the documentation is.** Each package now
53
+ carries a Documentation section pointing at the reference, at the report schema
54
+ that normatively specifies what a report may declare, and at the package's own
55
+ API. The paragraphs that used to end on an unstated contract — the event
56
+ stream's field semantics, the style vocabulary, page columns, the Content
57
+ Security Policy a fragment with images needs, the formula mangling, and each
58
+ target's own contract — link the page that states it. Every link is an absolute
59
+ URL, so it resolves from the npm package page as readily as from an installed
60
+ copy.
61
+ - Updated dependencies
62
+ - quario@0.8.0
63
+
64
+ ## 0.7.0
65
+
66
+ ### Minor Changes
13
67
 
14
68
  - **Styled runs flatten to their joined text.** A cell value written as a list of
15
69
  styled runs writes the runs' text as one field. This target withdraws every
16
70
  style, and a run's is no different.
17
71
 
18
- ## [0.6.0] - 2026-09-07
72
+ ## 0.6.0
19
73
 
20
- ### Changed
74
+ ### Minor Changes
21
75
 
22
- - Requires quario ^0.6.0
76
+ - Updated dependencies
77
+ - quario@0.6.0
23
78
 
24
- ## [0.5.0] - 2026-09-05
79
+ ## 0.5.0
25
80
 
26
- ### Added
81
+ ### Minor Changes
27
82
 
28
83
  - **Spans keep the record rectangular.** A cell covering several columns writes
29
84
  its value in the first field it covers and leaves the rest empty, so every
30
85
  record is the same width. This target reads no geometry, so there is nothing
31
86
  here for a merge to mean.
32
87
 
33
- ## [0.4.0] - 2026-09-03
88
+ ## 0.4.0
34
89
 
35
- ### Changed
90
+ ### Minor Changes
36
91
 
37
- - Requires quario ^0.4.0
92
+ - Updated dependencies
93
+ - quario@0.4.0
38
94
 
39
- ## [0.3.0] - 2026-09-02
95
+ ## 0.3.0
40
96
 
41
- ### Changed
97
+ ### Minor Changes
42
98
 
43
99
  - **A table total emits N records.** Each `total-row` is one record, as
44
100
  each data row is.
45
-
46
101
  - **`format` is unread.** Bare interpolations stay `1000` / ISO dates, as
47
102
  every other style is unread here.
48
-
49
103
  - **`page.margin` and report-header `height` are unread.** This target has
50
104
  no page to inset or pin on.
51
105
 
52
- ## [0.2.0] - 2026-09-01
106
+ ## 0.2.0
53
107
 
54
- ### Added
108
+ ### Minor Changes
55
109
 
56
110
  - **A split is one record, its slots the fields.** Slots are values beside
57
111
  each other, which is what a record is. Slot widths go unread, as every width
58
112
  is here. Images stay unread too, but a slot holding one still writes an
59
113
  empty field, so the fields either side keep their positions.
60
-
61
- ### Changed
62
-
63
114
  - **Display-text `Date`s join as ISO 8601 UTC.** A `Date` inside a mixed
64
115
  cell (literal text plus interpolation) joined through `String(date)`, so
65
116
  the field carried the host's timezone and locale. It now joins through the
@@ -67,9 +118,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
67
118
  typed single-`Date` field has always used, so every `Date` in a CSV now
68
119
  reads the same way.
69
120
 
70
- ## [0.1.0] - 2026-08-27
121
+ ## 0.1.0
71
122
 
72
- ### Added
123
+ ### Minor Changes
73
124
 
74
125
  - **CSV records in render order.** A bare interpolation stays typed text (a
75
126
  number's decimal, a Date as ISO 8601 UTC); everything else is display text,
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @quario/csv
2
2
 
3
3
  **The CSV render target for [quario](https://www.npmjs.com/package/quario).** Renders a report
4
- definition to CSV records with typed fields: a bare number stays a number's decimal, a Date
5
- stays ISO 8601 UTC, and text that would open as a spreadsheet formula is neutralized.
4
+ definition to CSV records with typed fields. A bare number stays a number's decimal, and a Date
5
+ stays ISO 8601 UTC. This target neutralizes text that would open as a spreadsheet formula.
6
6
 
7
7
  ## Install
8
8
 
@@ -11,7 +11,7 @@ npm install quario @quario/csv
11
11
  ```
12
12
 
13
13
  The engine is a peer dependency, installed beside the target. ESM-only, Node 22+, and
14
- browser-ready through any standards-based ESM bundler. The renderer returns a string; you
14
+ browser-ready through any standards-based ESM bundler. The renderer returns a string. You
15
15
  encode the file.
16
16
 
17
17
  ## Quick start
@@ -54,15 +54,16 @@ Chair,120
54
54
  Lamp,40
55
55
  ```
56
56
 
57
- Any schema the other targets render works here unchanged. A CSV file has no pages, so page
58
- bands are ignored. Interpolate the bare value, not a formatter, when you want a typed field:
59
- `{{ @.amount }}` writes `1000`; `{{ currency(@.amount) }}` writes text.
57
+ Any schema the other targets render works here unchanged. A CSV file has no pages, so this target
58
+ ignores page bands. Interpolate the bare value, not a formatter, when you want a typed field.
59
+ `{{ @.amount }}` writes `1000`, and `{{ currency(@.amount) }}` writes text.
60
60
 
61
61
  ## API
62
62
 
63
63
  ### `csv()`
64
64
 
65
- The target factory takes no options and returns the target you pass to `render`.
65
+ The target factory takes no options and returns the target you pass to `render`. It refuses an argument rather than
66
+ discarding one. It throws a `TypeError` if you give it one.
66
67
  `report()` compiles once and `report.render(csv(), data)` resolves the string. Compile at
67
68
  startup, render per request. Definition problems throw at `report()`, at compile time.
68
69
 
@@ -75,7 +76,7 @@ The compiled report carries `stream` (the raw event generator), `names`, `functi
75
76
  `paths`, like every quario report. Engine-level options (`query` budgets, the license key)
76
77
  live on the instance, and `q.license` settles with the verification result.
77
78
 
78
- Rendering is asynchronous and hands the loop back between batches, so a large report never
79
+ Rendering is asynchronous and returns the loop between batches, so a large report never
79
80
  blocks the host. Render-time failures reject with located errors.
80
81
 
81
82
  ## Mapping
@@ -83,12 +84,14 @@ blocks the host. Render-time failures reject with located errors.
83
84
  The banded walk flattened as records in render order: report-header items, then per group
84
85
  instance its header items, nested content, and footer items, then the table's header record,
85
86
  data records, and total records, then report-footer items. Grouping exists as that order
86
- alone. Group boundaries add no records of their own, and `break: "page"` and `reset: "page"`
87
- are ignored. This target is unpaginated: `report-start.page` band closures are ignored.
87
+ alone. Group boundaries add no records of their own, and this target ignores `break: "page"`
88
+ and `reset: "page"`. This target paginates nothing, so it ignores `report-start.page` band
89
+ closures.
88
90
 
89
- Each item takes one record with one field; a split takes one record with a field per slot, and a
90
- spanning cell writes empty fields for the columns it covers so positions hold. A report that is one
91
- table and no other bands has its column headers as record 1; any other band makes the file ragged.
91
+ Each item takes one record with one field. A split takes one record with a field per slot, and a
92
+ spanning cell writes empty fields for the columns it covers so positions hold. A report that is
93
+ one table and no other bands has its column headers as record 1. Any other band makes the file
94
+ ragged.
92
95
 
93
96
  ### Typed fields
94
97
 
@@ -97,25 +100,26 @@ number as ECMAScript ToString, a boolean as `true`/`false`, a `Date` as ISO 8601
97
100
  other cell joins to display text with `text()`, then the injection guard, then RFC 4180
98
101
  quoting.
99
102
 
100
- **Display-text fields that would open as spreadsheet formulas are mangled.** A leading `=`,
101
- `+`, `-`, `@`, TAB, or CR gets an apostrophe. A consumer that parses the file back does not
103
+ **This target mangles display-text fields that would open as spreadsheet formulas.** A leading
104
+ `=`, `+`, `-`, `@`, TAB, or CR gets an apostrophe. A consumer that parses the file back does not
102
105
  round-trip those fields. The XLSX target does the opposite: it writes an inert string and
103
- does not mangle.
106
+ does not mangle. See
107
+ [The CSV target](https://getquario.com/docs/diving-deeper/csv-target/#design-decisions).
104
108
 
105
109
  A hidden cell keeps its column slot as an empty field.
106
110
 
107
111
  ### Withdrawn, and the unread page bands
108
112
 
109
- `style`, column `width`, slot `width`, page columns and images are withdrawn: a record carries no
110
- presentation, no width and no page, so there is nothing here for any of them to resolve against. An
111
- image emits no record, though an image in a slot still writes its empty field. Page bands are the
112
- one unread declaration this target never calls their closures, so an expression inside one is
113
- never evaluated and never raises here.
113
+ This target withdraws `style`, column `width`, slot `width`, page columns and images. A record
114
+ carries no presentation, no width and no page, so there is nothing here for any of them to
115
+ resolve against. An image emits no record, though an image in a slot still writes its empty
116
+ field. Page bands are the one unread declaration. This target never calls their closures, so an
117
+ expression inside one never runs and never raises here.
114
118
 
115
119
  ### Text shape
116
120
 
117
121
  No BOM. Records separated by LF, not CRLF. The string ends with a newline. Quoting follows
118
- RFC 4180; the line ending does not.
122
+ RFC 4180. The line ending does not.
119
123
 
120
124
  ## Unlicensed marking
121
125
 
@@ -127,12 +131,23 @@ presence is normative. That a parser who stops before EOF will see it is best-ef
127
131
 
128
132
  Same schema and data produce an identical string.
129
133
 
134
+ The full contract is [The CSV target](https://getquario.com/docs/diving-deeper/csv-target/),
135
+ with each declaration's fate in the
136
+ [support matrix](https://getquario.com/docs/reference/support-matrix/).
137
+
138
+ ## Documentation
139
+
140
+ [The quario documentation](https://getquario.com/docs/) is the reference.
141
+ The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
142
+ specification of what a report may declare, and
143
+ [`@quario/csv`](https://getquario.com/docs/reference/csv/) is this package's own API.
144
+
130
145
  ## License
131
146
 
132
- Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
133
- licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
147
+ Commercial software with readable source. Evaluation is free, unlimited, and watermarked.
148
+ Per-developer licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
134
149
 
135
- Pass your license key once, on the instance; it is verified offline:
150
+ Pass your license key once, on the instance. quario verifies it offline:
136
151
 
137
152
  ```js
138
153
  const q = quario({ license: "quario_..." });
package/lib/index.d.ts CHANGED
@@ -4,5 +4,8 @@ import type { Target } from "quario";
4
4
  * The CSV render target:
5
5
  * `quario().report(schema).render(csv(), data)` resolves the records
6
6
  * described in SCHEMA.md ("The CSV target").
7
+ *
8
+ * It takes no options, and an argument is a `TypeError` at the call rather
9
+ * than an argument quietly discarded.
7
10
  */
8
11
  export function csv(): Target<"csv", Promise<string>>;
package/lib/index.js CHANGED
@@ -32,10 +32,20 @@ let field = (cell) => {
32
32
  * string (the host encodes the file). Rendering runs the engine's `walk`
33
33
  * driver over the handlers below, so large reports stay cooperative.
34
34
  *
35
+ * This target reads no host options, and refuses an argument rather than
36
+ * discarding one: a host writing `csv({ delimiter: ";" })` believes something
37
+ * about the output that is not true, and nothing later would tell them
38
+ * (`docs/adr/0072`). The rule the other targets state over a key set, this
39
+ * one states over the whole call. Nullish is absence here as everywhere, so
40
+ * `csv(config.options)` over a config carrying none is not a mistake.
41
+ *
42
+ * @param {...never} rest Nothing: an option is a definition error.
35
43
  * @returns {{name: "csv", compile: (stream: any) => (data?: any) => Promise<string>}}
36
44
  * The target (see SCHEMA.md, "Instances and targets").
37
45
  */
38
- export function csv() {
46
+ export function csv(...rest) {
47
+ if (rest.some((option) => option != null))
48
+ throw TypeError("options: this target takes no options");
39
49
  return {
40
50
  name: "csv",
41
51
  compile: (stream) => async (data) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quario/csv",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "The CSV render target for quario — in the makings, not yet released",
5
5
  "homepage": "https://getquario.com",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -38,12 +38,12 @@
38
38
  "devDependencies": {
39
39
  "@arethetypeswrong/cli": "^0.18.3",
40
40
  "@size-limit/preset-small-lib": "^13.0.3",
41
- "quario": "^0.7.0",
41
+ "quario": "^0.9.0",
42
42
  "size-limit": "^13.0.3",
43
43
  "typescript": "^7.0.2"
44
44
  },
45
45
  "peerDependencies": {
46
- "quario": "^0.7.0"
46
+ "quario": "^0.9.0"
47
47
  },
48
48
  "size-limit": [
49
49
  {