@quario/csv
v0.9.1
Published
Tiny, dependency-free CSV render target for quario. Typed fields, and text that cannot open as a formula.
Maintainers
Readme
@quario/csv
The CSV render target for quario. Renders a report definition to CSV records with typed fields. A bare number stays a number's decimal, and a Date stays ISO 8601 UTC. This target neutralizes text that would open as a spreadsheet formula.
Install
npm install quario @quario/csvThe engine is a peer dependency, installed beside the target. ESM-only, Node 22+, and browser-ready through any standards-based ESM bundler. The renderer returns a string. You encode the file.
Quick start
import { writeFileSync } from "node:fs";
import { csv } from "@quario/csv";
import { quario } from "quario";
const schema = {
data: "$.orders[*]",
detail: {
columns: [
{ header: "Product", value: "{{ @.product }}" },
{ header: "Price", value: "{{ @.price }}" },
],
},
};
const data = {
orders: [
{ product: "Desk", price: 250 },
{ product: "Chair", price: 120 },
{ product: "Lamp", price: 40 },
],
};
const report = quario().report(schema);
const records = await report.render(csv(), data);
writeFileSync("sales.csv", records);A licensed render of that table-only report:
Product,Price
Desk,250
Chair,120
Lamp,40Any schema the other targets render works here unchanged. A CSV file has no pages, so this target
ignores page bands. Interpolate the bare value, not a formatter, when you want a typed field.
{{ @.amount }} writes 1000, and {{ currency(@.amount) }} writes text.
API
csv()
The target factory takes no options and returns the target you pass to render. It refuses an argument rather than
discarding one. It throws a TypeError if you give it one.
report() compiles once and report.render(csv(), data) resolves the string. Compile at
startup, render per request. Definition problems throw at report(), at compile time.
const report = quario().report(schema, funcs);
const records = await report.render(csv(), data);The compiled report carries stream (the raw event generator), names, functions, and
paths, like every quario report. Engine-level options (query budgets, the license key)
live on the instance, and q.license settles with the verification result.
Rendering is asynchronous and returns the loop between batches, so a large report never blocks the host. Render-time failures reject with located errors.
Mapping
The banded walk flattened as records in render order: report-header items, then per group
instance its header items, nested content, and footer items, then the table's header record,
data records, and total records, then report-footer items. Grouping exists as that order
alone. Group boundaries add no records of their own, and this target ignores break
and reset. This target paginates nothing, so it ignores report-start.page band
closures.
Each item takes one record with one field. A split takes one record with a field per slot, and a spanning cell writes empty fields for the columns it covers so positions hold. A report that is one table and no other bands has its column headers as record 1. Any other band makes the file ragged.
Typed fields
A cell whose value is one bare interpolation writes its pre-stringify value as text: a finite
number as ECMAScript ToString, a boolean as true/false, a Date as ISO 8601 UTC. Every
other cell joins to display text with text(), then the injection guard, then RFC 4180
quoting.
This target mangles display-text fields that would open as spreadsheet formulas. A leading
=, +, -, @, TAB, or CR gets an apostrophe. A consumer that parses the file back does not
round-trip those fields. The XLSX target does the opposite: it writes an inert string and
does not mangle. See
The CSV target.
A hidden cell keeps its column slot as an empty field.
Withdrawn, and the unread page bands
This target withdraws style, column width, slot width, page columns and images. A record
carries no presentation, no width and no page, so there is nothing here for any of them to
resolve against. An image emits no record, though an image in a slot still writes its empty
field. Page bands are the one unread declaration. This target never calls their closures, so an
expression inside one never runs and never raises here.
Text shape
No BOM. Records separated by LF, not CRLF. The string ends with a newline. Quoting follows RFC 4180. The line ending does not.
Unlicensed marking
An unlicensed render writes the wording from report-start.marking as one trailing line, one
field, through the same quote and guard as any field. A licensed render omits it. The line's
presence is normative. That a parser who stops before EOF will see it is best-effort.
Determinism
Same schema and data produce an identical string.
The full contract is The CSV target, with each declaration's fate in the support matrix.
Documentation
The quario documentation is the reference.
The report schema is the normative
specification of what a report may declare, and
@quario/csv is this package's own API.
License
Commercial software with readable source. Evaluation is free, unlimited, and watermarked. Per-developer licenses at getquario.com. See the bundled LICENSE.
Pass your license key once, on the instance. quario verifies it offline:
const q = quario({ license: "quario_..." });
await q.license; // { licensed: true, licensee: "Acme BV", id: "1-ACME" }