jasy

jasy

ZUGFeRD & XRechnung e-invoices in pure TypeScript - generate, validate, read.
A Flutter-style PDF engine underneath. The EN-16931 maths done for you. Zero Java, zero upload.

npm i @jasy/e-invoice  ·  npx @jasy/cli  ·  MIT

--- ## Don't believe us. Point it at your own invoice. ```bash npx @jasy/cli validate ./your-invoice.pdf ```

jasy validate: VALID

That `✓` is the **same official KoSIT Schematron + veraPDF the big players run** - against the EN 16931 **and** the German XRechnung (BR-DE) rules, plus PDF/A-3. Your file, your terminal, in seconds. No account, no upload, nothing leaves your machine. > Java has **Mustang**. PHP has **horstoeko**. Python has **factur-x**.
> Node had nothing. **Now it has jasy.** --- ## You bring the line items. jasy does the rest. You never compute a total, a VAT breakdown or a rounding again. You hand jasy the line items - it **derives** the document totals and the EN-16931 VAT breakdown, spec-correct. That is _why_ the invoices validate: the amounts are correct **by construction**, so the single biggest class of EN-16931 failures (the BR-CO total checks) simply cannot happen. ```ts import { renderZugferd } from "@jasy/e-invoice"; const { bytes, xml } = await renderZugferd({ number: "RE-2026-001", issueDate: "2026-06-17", currency: "EUR", seller: { name: "Northwind Studio GmbH", vatId: "DE123456789", address: { city: "Berlin", postCode: "10115", country: "DE" }, }, buyer: { name: "Globex Corporation Ltd", address: { city: "Munich", postCode: "80331", country: "DE" }, }, lines: [ { name: "Brand identity design", quantity: 2, unit: "HUR", netUnitPrice: 100, vat: { category: "S", ratePercent: 19 }, }, ], }); // bytes -> a valid ZUGFeRD PDF/A-3 · xml -> the embedded EN-16931 XML // totals, tax breakdown and rounding: computed for you. ``` One object in. A conformant, human-readable PDF/A-3 **and** the EN-16931 CII/UBL XML out. ZUGFeRD and XRechnung work out of the box - no config, no template wrangling.

a generated ZUGFeRD invoice

--- ## The whole loop, from the terminal ```bash jasy read invoice.pdf # identify + show: parties, line items, totals jasy validate invoice.pdf # EN 16931 + XRechnung + PDF/A (exit 1 if invalid) jasy export invoice.pdf -o out.xlsx # read it back: JSON · TXT · Excel ```

the jasy terminal: invoice + checks + export

Reads **CII and UBL**, real third-party invoices included - not just our own output. --- ## A PDF engine that reads like Flutter Underneath it all is a declarative, component-based PDF engine - written from the byte stream up, no headless browser, no `pdf-lib`. You describe the document; it lays it out and paginates. ```ts import { Document, Page, Column, Text, renderToBytes } from "@jasy/pdf"; const pdf = await renderToBytes( Document([ Page({ size: "A4", margin: 48 }, [ Column({ gap: 8 }, [ Text("Invoice RE-2026-001", { size: 24, bold: true, color: "steelblue" }), Text("Thank you for your business.", { color: "gray" }), ]), ]), ]), ); ``` Real text layout (real font metrics, kerning, ligatures, word-wrap), full flexbox (`gap` / `justify` / `align`, wrap, `flexBasis`, `flexShrink`, `order`), boxes with per-corner radius, gradients and alpha, images and **SVG**, a `Canvas` to draw into, custom fonts from a file, bytes or a URL, AcroForm fields, **AES-256 password encryption**, tagged PDFs for screen readers, and **real pagination** - content that overflows flows onto the next page, headers and footers repeat, and a block can refuse to be split. --- ## Under the hood - and every bit is real This is not a wrapper around someone else's renderer. It is built from the byte stream up, and you can verify all of it: - **Hand-rolled PDF writer.** No `pdf-lib`, no PDFKit, no Java, no headless Chrome - the byte stream is ours. - **Font subsetting.** Only the glyphs you actually use are embedded, with the PDF/A subset tag - a 740 KB font ships as a ~76 KB subset, and the text stays copy- and searchable. - **Compression.** Content streams and images are FlateDecode-compressed; the spreadsheets `jasy export` writes are real `.xlsx` ZIPs we deflate with our own writer and CRC32, zero dependencies. - **AES-256 encryption, pure JS.** Lock a PDF with `renderToBytes(doc, { encrypt: { userPassword } })` - the newest standard handler (AES-256, R6 / ISO 32000-2) via the platform WebCrypto: no native crypto, no deps, the same code in Node **and** the browser. Owner password + permissions optional. - **Real font metrics.** Text is laid out with the Adobe AFM metrics of the standard-14 fonts - kerning and word-wrap are _computed_, not guessed. - **PDF/A-3, matched not approximated.** The conformance graph is hand-built and **passes veraPDF**, the official ISO 19005 validator. - **Byte-exact round-trips.** Generate and parse are inverses: `generate → parse → regenerate` reproduces the identical XML. Over 1,600 tests hold the line across the workspace. - **Schematron, local.** The official EN-16931 + XRechnung rules run via saxon-js (the real XSLT, in pure JS) - no Java, no upload. - **Vectors, not bitmaps.** `Image("logo.svg")` keeps a logo a VECTOR - our own SVG reader and our own XML parser, checked against 10,819 real files and compared pixel for pixel with headless Chrome. Need a chart instead? `Canvas` hands you a typed pen for the same vector layer. - **Fonts, whatever the container.** `.ttf`, `.woff` and `.woff2` are the same thing at the call site. The WOFF2 container and its `glyf` transform are ours; only Brotli is a dependency, and it is loaded only if a WOFF2 actually turns up. - **Byte-stable output.** The same document rendered twice is byte-identical, PDF/A and ZUGFeRD included, across separate processes. An archived invoice stays re-derivable and hashable years later. - **Accessible if you ask.** `renderToBytes(doc, { accessible })` emits a full tagged structure tree, and the output passes veraPDF's machine-verifiable PDF/UA-1 checks. --- ## Packages | Package | What it is | | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | **[@jasy/e-invoice](https://npmx.dev/@jasy/e-invoice)** | ZUGFeRD / XRechnung: your data → PDF/A-3 + EN-16931 XML, with local validation. **The prize.** | | **[@jasy/cli](https://npmx.dev/@jasy/cli)** | the `jasy` terminal: read · validate · export, headless **and** interactive | | **[@jasy/pdf](https://npmx.dev/@jasy/pdf)** | the declarative, Flutter-style PDF layout engine that powers them | | **[@jasy/vue](https://npmx.dev/@jasy/vue)** | author PDFs as Vue components - and render them in the browser | | **[@jasy/nuxt](https://npmx.dev/@jasy/nuxt)** | the Nuxt module: zero-config PDFs, client **or** server | --- ## Why you can trust it - **Over 1,600 tests, green** across the workspace (1,190 in the engine alone). The generator and the parser are **byte-exact inverses**: `generate → parse → regenerate` reproduces the identical XML. Nothing is silently lost. - **The same rules the authorities use** - the official KoSIT EN-16931 + XRechnung Schematron, and the official **veraPDF** for the full ISO 19005 (PDF/A) check. - **Proven on real third-party invoices**, not only our own. - **100% local.** No upload, no service, no account - DSGVO-safe by construction. --- ## Honest scope jasy targets the documents that matter here: invoices, reports, quotes, datasheets - and it now sets text properly: kerning and Latin ligatures from the font's own tables, justification, pluggable hyphenation, right-to-left and Arabic shaping, underlines that step around descenders. Pagination is real: text splits at line boxes, `keepTogether` moves a card whole, orphans and widows are honoured. It is still **not** a LaTeX or WeasyPrint replacement, and does not try to be: no automatic microtypography, no floats, no CSS engine. What it will not draw, it **names** - an SVG filter, a font without the glyph, a word that cannot be split - rather than quietly drawing something else. > **Status:** young and pre-1.0. The API can still shift between minor versions. Everything shown here > works and is tested. ---

MIT  ·  built by Florian Heuberger