Invoice Totals

An invoice calculator split the way a real project often is: the arithmetic in Refingere, where every amount is an exact number of cents, and the presentation in JavaScript, which already knows how every locale writes money - and a TypeScript front end calls the arithmetic like any other module.

Exact money

Amounts are U64 cents, and integers keep their type, so nothing is ever rounded by accident - only the tax, half up, once. A total too large for a U64 is an OverflowError, passed up with !!, never a wrong number - the third test checks it.

Both ways with @js

@js joins a function to JavaScript, and which way depends on whether it has a body. formatMoney, systemLocale and share have none: they're declarations of JavaScript functions, money.ts's exports - @js("./money.ts", "formatShare") gives the export a name of its own. The U64 cents arrive in JavaScript as a number - or a bigint past 253 - and the string that comes back is a String. totals and receipt have bodies: they're Refingere functions, and @js makes them callable from JavaScript. See JavaScript and TypeScript.

Building it

--run and --test see that the program calls JavaScript and run it on Bun. Compiled without --js, invoice.wasm is a plain WASI program for wasmtime - there, totals and receipt are ordinary functions, and only a call reaching one of the JavaScript declarations stops, saying so.

Called from TypeScript

--compile --js writes a loader for totals and receipt, invoice.mjs, with money.ts bundled in, and invoice.d.mts with their types - an Invoice is an object type with a lines array, just as the struct has it. The object passed in becomes an Invoice, each quantity a U32, and a total that overflows would be thrown as an Error.

Concepts shown

  • @js declarations calling a TypeScript module.
  • @js definitions called from TypeScript, through a generated loader and types.
  • Exact integer money with U64, and overflow as an error.
  • One program as a standalone WASI module and as a module for JavaScript.