npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fin.cx/compatibility

v5.0.1

Published

Exchange formats of third-party accounting systems: the EXTF Buchungsstapel in Windows-1252 on the rows of @fin.cx/skr, and the document-image archive (document.xml and ZIP)

Readme

@fin.cx/compatibility

Exchange formats of third-party accounting systems: the files a German tax advisor's system imports. The package's name carries no vendor's name; the formats and files keep the names the importing system expects (EXTF, DATEV_Belege_<label>.zip, the Datev field names in @fin.cx/skr's rows). Two formats, each on its own entry point:

  • @fin.cx/compatibility/extf: the EXTF Buchungsstapel. The rows and the text come from @fin.cx/skr (journalDraftsToDatevRows, writeExtfBuchungsstapel); this module checks the drafts, writes the file in Windows-1252 and says which characters the encoding could not hold.
  • @fin.cx/compatibility/belegbilder: the document-image archive, a DATEV Document-Package: document.xml after DATEV's Document_v060.xsd plus one <GUID>.<extension> per document (a PDF or a picture), as one ZIP, linked to the bookings through the GUID in their Beleglink (field 20, BEDI "GUID").

It reads no clock, keeps no state and writes nothing anywhere: files go back to the caller as bytes.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Install

pnpm install @fin.cx/compatibility @fin.cx/skr

@fin.cx/skr (^7.0.1) is a peer dependency: the drafts and the account policy the EXTF writer takes are built by your copy of skr, and the writer turns them into rows with the same copy, so the rows follow the chart the drafts were booked on (which accounts are Automatikkonten, which carry VAT). A second copy of skr of another major would write rows by another chart; a package manager that finds your skr outside the range says so as an unmet peer.

Usage

import * as skrCore from '@fin.cx/skr/core';
import { extf } from '@fin.cx/compatibility';
// or: import * as extf from '@fin.cx/compatibility/extf';

const file = extf.writeBuchungsstapel({
  drafts, // skrCore.IJournalDraft[] of the period
  policy: skrCore.SKR03_DEFAULT_POLICY,
  header: {
    createdAt: new Date(),
    consultantNumber: 1001,
    clientNumber: 1,
    fiscalYearStart: new Date('2026-01-01T00:00:00Z'),
    dateFrom: new Date('2026-03-01T00:00:00Z'),
    dateTo: new Date('2026-03-31T00:00:00Z'),
    label: 'Buchungsstapel 2026-03',
    festschreibung: false,
  },
  filename: extf.extfFilename('2026-03'), // EXTF_Buchungsstapel_2026-03.csv
});
file.bytes; // Uint8Array in Windows-1252, served as extf.EXTF_CONTENT_TYPE
file.bookings; // what each row says as the file states it, for keeping and comparing
file.replacedCharacters; // [{ offset, character: 'ő', row: 1, field: 14 }, …]: written as "?"
  • Where a character that became ? stands. Each entry of replacedCharacters names its offset in the text and, for a booking row, the index of that row in rows (from 0) and the number of its DATEV field (from 1; 14 is the Buchungstext). A character of the header line (the label) has neither.

  • The same without a file. extf.replacedCharactersOfRows(rows) names the characters of each row the file could not hold ({ row, field, character }), without a header and without writing anything: for a preview, before the consultant and client numbers of the header are known. It reads each row's line as the writer writes it (@fin.cx/skr datevRowFields), so a character a field is cut off before (the Buchungstext keeps 60 characters) is not named.

  • A draft the account policy refuses throws CompatibilityError with code invalid_bookings and a message naming the draft.

  • What the reader would refuse is refused before writing. A header whose period ends before it begins or leaves the Wirtschaftsjahr beginning on its Wirtschaftsjahr-Beginn throws code invalid_header; a row whose Belegdatum lies outside that period, is no day, or whose amount is not positive throws invalid_bookings, naming the row.

  • A failed conversion throws code export_failed.

  • A document-image archive DATEV would not take (no document, a GUID of another form, a format it does not take) throws code invalid_documents.

  • differingBookingCount(left, right) counts the rows two Stapel of one period do not share. It compares each booking's day, amount, side, accounts, BU key, Belegfeld 1, cost centres (KOST1, KOST2) and whether it is a general reversal, as a multiset, and ignores wording and document links. A booking stored before 4.1 has no cost-centre fields: its record says nothing of them, and they never make it differ; compare bookingsOfRows of the rows sent for an answer that counts them.

  • The bookings as the file states them. extf.bookingsAsWritten(rows) (and extf.rowsAsWritten(rows)) writes the rows with skr's writer, encodes them in Windows-1252 and reads them back with skr's reader: a " in a text field becomes ', Belegfeld 1 is written as skr's writer writes it (cut to 36 characters before skr 7.3.0; since then a value DATEV does not take is mapped, datevBelegfeldOf, with the original in a Beleginfo), a line break becomes a space and a character Windows-1252 cannot hold becomes ?, as in the file. writeBuchungsstapel's bookings are read back from the bytes it wrote. differingBookingCount and a correction take both sides as a file states them, so bookings stored before writing (4.1 and before), bookings of this version and rows read back from the file sent compare alike; compare with them, not with bookingsOfRows of rows that were not written, which keep what the file does not.

  • A period whose bookings were stored before 4.1. Such bookings record no cost centres, although the Stapel sent stated them (fields 37 and 38), and differingBookingCount never counts them as differing on cost centres alone: a change of a cost centre only is not seen. For such a period read the file handed over back (readExtfBuchungsstapel(bytes).rows) and compare bookingsOfRows of those rows, or pass them to a correction as handedOverRows.

  • A correction of a period handed over. writeCorrectionBuchungsstapel({ handedOver, drafts, policy, header, filename }) writes only what changed since the state handed over: each booking handed over that the drafts no longer hold is written again with field 118 "Generalumkehr" set, so DATEV takes it back on its own side and day, then each row of the drafts that was not handed over; what stands in both is not written. A changed text or document link is no change; a changed cost centre is, so the booking is taken back on the cost centre DATEV received and booked on the new one. Every row keeps its Belegdatum, so it counts in the period it belongs to; a booking handed over is never replaced (§ 146 Abs. 4 AO), which also holds once DATEV has fixed the period (Festschreibung). The file names what it takes back (reversed), what it books (added) and the bookings after it (bookingsAfter: each booking it keeps as it was handed over, text and document link included, then the rows it books anew as the correction file states them). Both sides are compared as a file states them, so a reference the writer changes (a ", more than 36 characters, a character Windows-1252 cannot hold, a line break) is no change. correctionOf(handedOver, rows) gives reversed, kept and added without a file, and rowOfBooking(booking) the row a booking was written as.

  • The state handed over is given once: the bookings stored of it (handedOver: the bookings of its Buchungsstapel as they were stored, by this version or by 4.1, or the last correction's bookingsAfter), or the rows of the Stapel as it was sent (handedOverRows: the transmitted file read back with @fin.cx/skr readExtfBuchungsstapel(bytes).rows, or the rows given to the writer). Every form compares alike. A booking stored before 4.1 has no cost-centre fields, although the Stapel sent stated them (fields 37 and 38): a correction refuses it with code handed_over_cost_centres_unknown, naming it, since whether it must be taken back turns on the cost centres DATEV holds and a reversal without them would not mirror it; pass handedOverRows for such a period. Nothing changed is refused with nothing_to_correct; a general reversal in the state handed over (a correction file passed as the state), or a state given twice or not at all, with invalid_bookings.

  • A booking keeps its general reversal (IExportBooking.generalumkehr, from field 118) and its cost centres (kost1, kost2, empty where the row states none).

import { belegbilder } from '@fin.cx/compatibility';

const zip = await belegbilder.createBelegbilderZip(
  [
    { guid, description: 'Rechnung 7', bytes: pdfBytes },
    { guid: photoGuid, description: 'Kassenbon', bytes: jpegBytes, format: 'jpeg' },
  ],
  { createdAt: new Date(), generatingSystem: 'My app' },
);
// served as belegbilder.BELEGBILDER_CONTENT_TYPE under belegbilder.belegbilderFilename('2026-03')

The archive is what DATEV's XML-Schnittstelle online takes as a Document-Package (developer.datev.de, "DATEV XML interface online", format specification and XSD files, read 2026-09-28):

  • document.xml in the namespace http://xml.datev.de/bedi/tps/document/v06.0, version="6.0", with the header's date (createdAt's UTC clock time without a zone, as the Buchungsstapel header writes its own; the package reads no clock) and, when given, generatingSystem (up to 40 characters).
  • One <document guid="…"> per entry, its description cut to DATEV's 40 characters (none when empty), and one <extension xsi:type="File" name="<GUID>.<extension>"/>. The GUID is what DATEV Unternehmen online files the document under and what the Beleglink names; it must have the RFC 4122 form 8-4-4-4-12 hexadecimal digits.
  • format is pdf (the default), jpeg, png, gif, tiff or bmp (BELEGBILD_FORMATS), written as .pdf, .jpg, .png, .gif, .tif, .bmp (belegbildFileName). DATEV takes these among its File types (PDF, XML, TIF, TIFF, BMP, CSV, DOC, DOCX, GIF, JPEG, JPG, ODS, ODT, PKCS7, PNG, RTF, TXT, XLS, XLSX); it does not take WebP, AVIF or HEIC, so a picture in one of them must be converted by the caller first.
  • One file per GUID: a document listed twice (the same document linked by two bookings: the same GUID, format and bytes) is in the archive once, under its first description.
  • Characters XML 1.0 does not allow (C0 controls other than tab, line feed and carriage return, a surrogate not in a pair, U+FFFE, U+FFFF) are left out of a description and of generatingSystem before they are cut.
  • Refused with CompatibilityError invalid_documents: no document, more than 4999 (the schema's bound), a GUID of another form, a format outside BELEGBILD_FORMATS, one GUID for two different files (another format, or other bytes).

Windows-1252

encodeWindows1252(text) follows the WHATWG Encoding Standard. Bytes 0x00–0x7F and 0xA0–0xFF are the code points of the same number, and 0x80–0x9F carry the 27 characters € ‚ ƒ „ … † ‡ ˆ ‰ Š ‹ Œ Ž ‘ ’ “ ” • – — ˜ ™ š › œ ž Ÿ.

A character outside the encoding becomes one ? and is listed in replaced, so nothing is lost unreported. This includes C1 controls, U+FFFD, and anything outside the Basic Latin and Latin-1 ranges other than those 27.

Compared with iconv-lite 0.7.3 over every code point of the Basic Multilingual Plane, the output is the same except in two cases:

  • U+FFFD: iconv-lite writes byte 0x9D, a C1 control; this module writes ?.
  • A character outside the BMP: iconv-lite writes ??, one per UTF-16 unit; this module writes one ?.

Open

  • The document.xml layout (<archive><content><document guid>…, no namespace) is the one this module took over from its first user. No primary description of the importing system's schema was available when it was written. The layout should be confirmed by one import at a tax advisor before it is relied on.

License and Legal Information

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license.md file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at [email protected].

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.