javascript and typescript, as one wasm package for every runtime

WebAssembly rather than a native Node addon: one artifact serves Node,
Deno, Bun, a browser and anything built on them, where napi would have
meant a prebuilt binary per platform and a source build for whoever the
matrix missed. The engine is pure computation and the menologion is
already compiled in, so there is nothing for it to give up.

A day comes back as a plain object, not a handle. A wasm class would
have meant a .free() that every caller eventually forgets, and this way
JSON.stringify works and the thing spreads into state. Two serializer
defaults had to be overridden to keep the promise that it is the same
document --json emits: maps as objects, and an absent field as null
rather than undefined, which stringify would otherwise drop entirely.

The TypeScript is hand-written, because the generated types for a value
crossing as JsValue are `any`, and a binding that types its whole
return as `any` is a binding TypeScript cannot check. tsc --strict is a release
gate now, the way mypy --strict already is, and the check includes three
lines that must fail to compile.

The one place this differs from every other binding: no remembered
default jurisdiction, because a browser has nowhere to keep one. It is
called out in the README, in the types and in docs/versioning.md.

The test resolves typikon through a staged node_modules rather than a
relative path, so what it exercises is the exports map, and it compares
a year of days field by field against the command line for four
jurisdictions.
This commit is contained in:
funkpower
2026-09-18 14:55:40 -04:00
parent d90d4c171f
commit 69907eb186
16 changed files with 1002 additions and 3 deletions
+23 -2
View File
@@ -72,6 +72,27 @@ and in Python, the module's functions and the attributes of `Day`.
`Day.to_dict()` in Python carries the same fields as the JSON, in snake_case
rather than camelCase, which is the one deliberate difference between them.
### The C ABI
Every `typikon_*` symbol declared in `include/typikon.h`, and the meaning of
what it returns. `typikon_abi_version()` is bumped when any of that changes;
additions leave it alone, so a caller can require a minimum and get everything
after it.
Dates cross as Julian Day Numbers and a day is opaque, so the ABI carries no
struct layout and a rebuilt library stays loadable against a header that has
not moved.
### The JavaScript package
The functions the npm package exports, and the `Day`, `Options` and
`JurisdictionInfo` types in its `.d.ts`. A day is the JSON above, with the same
field names; `undefined` never appears where the JSON has `null`.
The one deliberate difference from every other binding: there is no remembered
default jurisdiction, because a browser has nowhere to keep one. The default is
the New Calendar.
## What is not stable
- **The text output.** It is for a person to read, and it changes when a better
@@ -98,8 +119,8 @@ So: **every change to what a day reports is called out in the changelog under
"Calendar"**, with the days affected, whatever the version number does. If a
correction changes a day you care about, the changelog is where it will say so.
Adding a jurisdiction, a menaion or a tradition is a minor version. Correcting
an existing one is a patch, and is announced.
Adding a jurisdiction, a menaion, a tradition or a binding is a minor version.
Correcting an existing one is a patch, and is announced.
## Releasing