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

@open-nav/core

v0.3.0

Published

Types, invoice building, XML serialisation and cryptography for the Hungarian NAV Online Számla API

Readme

@open-nav/core

The parts of the NAV Online Számla interface that have a right answer: authentication, the schema, XML, exact decimal arithmetic and validation. No network access — everything here is pure, so it is testable and safe to run anywhere.

npm install @open-nav/core

Authentication

Every request NAV accepts carries a SHA-512 password hash and a SHA3-512 request signature. The signature is not over the request body: it is over requestId + yyyyMMddHHmmss + signKey, followed by a concatenation of per-invoice hashes in index order when the operation submits invoices.

import { passwordHash, requestSignature, createRequestId } from '@open-nav/core';

const requestId = createRequestId(); // safe against the 30 char / [+a-zA-Z0-9_] rule
const timestamp = new Date();

const signature = requestSignature(requestId, timestamp, process.env.NAV_SIGN_KEY!, [
  { index: 1, operation: 'CREATE', base64Payload: encoded },
]);
const hash = passwordHash(process.env.NAV_PASSWORD!); // the SHA-512 half

Verified against all 11 of NAV's published request samples, the three-invoice batch included, which is the case that catches an implementation that hashes in the wrong order or hashes a re-serialised copy of the payload.

decodeExchangeToken handles the other half: the token NAV returns is AES-128-ECB encrypted with your exchange key, and arrives padded or unpadded depending on the operation.

The schema, generated from the XSDs

The types and the runtime metadata that drives serialisation, parsing and validation are generated from NAV's own XSDs — ~700 elements across six namespaces — and must not be hand-edited. One descriptor table drives all three directions, so they cannot disagree with each other.

import {
  serializeDocument,
  parseDocumentAs,
  encodeInvoiceData,
  decodeInvoiceData,
} from '@open-nav/core';

const xml = serializeDocument('InvoiceData', document);
const back = parseDocumentAs<InvoiceData>(xml, 'InvoiceData');
const base64 = encodeInvoiceData(document); // what goes in the request

All 41 official NAV sample documents round-trip to canonically equal XML.

Namespaces are the trap worth naming: a tax number sits in OSA/3.0/data while its taxpayerId child sits in OSA/3.0/base. The generated descriptors know which, so you never write a prefix.

Exact arithmetic

Invoice amounts run to 18 digits and quantities to 10 decimal places. IEEE 754 doubles cannot carry either, and being one forint out rejects the whole batch, so amounts are decimal strings end to end and arithmetic goes through Decimal, which is BigInt-scaled.

import { Decimal, computeInvoiceSummary, checkInvoiceSummary } from '@open-nav/core';

Decimal.from('0.1').add('0.2').toString(); // '0.3', not 0.30000000000000004

const invoice = document.invoiceMain.invoice!; // an InvoiceType
const summary = computeInvoiceSummary(invoice); // per VAT rate, plus the totals
const findings = checkInvoiceSummary(invoice); // where it fails to reconcile

Summation is not the whole rule. A line that omits its VAT amount has it derived from the rate, a VAT exemption is identified by its legal case and not by the free text beside it, and a foreign-currency invoice needs a forint twin for every amount. Reproduces the summaries of 24 of NAV's 30 sample invoices; the remaining 6 are internally contradictory upstream — see conformance/README.md.

Validating before NAV does

Two layers, both local: the generated schema, and the business rules that are INVALID_... faults in NAV's catalogue.

import { validateInvoice, faultMessage } from '@open-nav/core';

const report = validateInvoice(document, { operation: 'CREATE', language: 'hu' });
if (!report.valid) {
  for (const issue of report.errors) {
    console.error(issue.path, issue.code, issue.message, issue.navMessage);
  }
}
faultMessage('ANNULMENT_IN_PROGRESS', 'hu'); // NAV's own wording, in hu, en or de

Findings carry NAV's own fault code where one exists, so an error you see locally is the error you would have seen remotely. The catalogue is generated from schemas/i18n/ — 236 codes, in Hungarian, English and German. It covers NAV's business validation codes; the technical ones (INVALID_SIGNATURE, INVALID_REQUEST_ID and the rest) are not in NAV's message files, and arrive with their own text on the response instead.

Rules NAV can only decide server-side — whether the invoice number is already used, whether the customer's tax number is live — are listed in LOCALLY_UNDECIDABLE rather than guessed at.

Warnings do not make a report invalid, and that distinction is load-bearing: the taxpayer-id check digit is a warning because 2 of the 4 tax numbers in NAV's own samples fail it while NAV itself validates against its registry. A validator that cries wolf gets switched off.

Also here

OPERATIONS, BASE_URLS, MAX_INVOICE_BATCH_SIZE, QUERY_PAGE_SIZE and the other interface constants; parseTaxNumber and the county/VAT-code tables; NAV's timestamp formats; and the NavError hierarchy the client throws.

Licence

MIT. Not affiliated with NAV.