A browser-only ONIX metadata composer. Write a book record, or open an existing ONIX file, and get valid XML back out — in ONIX 2.1, 3.0 or 3.1, in reference or short tags.
There is no backend. Every schema, code list, parse, validation, conversion and download happens in your browser; nothing is uploaded anywhere.
Live at onyxa.aodihis.com.
- Open or start fresh — drop in an existing ONIX file, or begin a new record.
- Reads what the trade actually sends — ONIX 2.1 and 3.0/3.1, reference tags
(
<ProductForm>) and short tags (<b012>), single- or multi-product files, namespaced or not. The version is detected from the file. - Converts between versions in both directions, and tells you which fields cannot survive the trip before you commit to it.
- Covers every field. 1,454 elements across the three releases, all editable, all round-trippable — proven by a check that fails the build otherwise.
- Validates as you type — required, conditional and code-list membership, against the full Issue 74 code lists.
| Version | Elements | In folios | In All fields | Round-trip | Gaps |
|---|---|---|---|---|---|
| ONIX 2.1 | 431 | 130 | 301 | 431 | 0 |
| ONIX 3.0 | 512 | 210 | 302 | 512 | 0 |
| ONIX 3.1 | 511 | 209 | 302 | 511 | 0 |
Rather than hand-writing ~500 form controls, the app generates a JSON element registry from EDItEUR's own schemas — reference name, short tag, parent path, cardinality, datatype, code list, sequence position — and renders forms from it. Eight curated folios cover the fields most records need; a ninth All fields view exposes everything else.
npm run check:coverage then asserts that every element in every registry is both editable
in the UI and survives a parse → serialize → parse round trip in both tag styles, and
fails otherwise. Because the registry is generated rather than written by hand, "all fields
are covered" is a measurement, not a claim. Latest run: dev_docs/coverage.md.
npm install
npm run dev| Script | Does |
|---|---|
npm run dev |
Dev server |
npm run build |
Static site into build/ — host it anywhere |
npm test |
Everything, coverage check included |
npm run test:unit |
Everything except the coverage check |
npm run check:coverage |
Coverage check alone; writes coverage-report.json and dev_docs/coverage.md |
npm run check |
svelte-check type checking |
npm run build:registry |
Regenerate the registries and code lists from vendor/ |
make build # docker build -t ghcr.io/aodihis/onyxa:latest .
make run # serve it on http://localhost:8080
make release TAG=v1 # build and push to GHCRThe image is a multi-stage build: Node compiles the static site, then nginx:alpine serves
it (~64 MB). nginx handles the SPA fallback — /editor and /preview are client-side
routes with no file behind them — and gzips the generated JSON, which is worth about 6.6:1
(elements.3.0.json is 160 KB raw, 24 KB over the wire).
The test suite runs inside the build, so a broken record model cannot be published.
Pushes to main run the suite, publish to GHCR, and deploy over SSH to a VPS running
Traefik. See .github/workflows/docker-publish.yml
and docker-compose.vps.yml.
Repository variables
| Variable | Value |
|---|---|
DOMAIN |
onyxa.aodihis.com — written to the host .env as APP_HOST |
APP_DIR |
/opt/apps/onyxa |
Repository secrets — currently placeholders; deploys will fail until they are real:
| Secret | Should be |
|---|---|
VPS_HOST |
Hostname or IP of the VPS |
VPS_USER |
SSH user to deploy as |
VPS_SSH_KEY |
Private half of a deploy keypair whose public half is in that user's ~/.ssh/authorized_keys |
gh secret set VPS_HOST --repo aodihis/onyxa --body "1.2.3.4"
gh secret set VPS_USER --repo aodihis/onyxa --body "deploy"
gh secret set VPS_SSH_KEY --repo aodihis/onyxa < ~/.ssh/onyxa_deployThe VPS needs Docker, the external traefik and shared-network networks, and a DNS A
record for onyxa.aodihis.com. The domain lives in the DOMAIN repository variable, so
changing it there moves the app on the next deploy.
docker-compose.vps.yml declares env_file: .env, so .env must exist next to it —
without it docker compose up fails and the container never starts, which shows up as
"not registered in Traefik". The deploy writes it every time; if you run compose by hand:
printf 'APP_HOST=onyxa.aodihis.com\nTAG=latest\n' > /opt/apps/onyxa/.envIf the site 404s or the router never appears, run scripts/diagnose-deploy.sh on the VPS.
All schema sources are vendored so the build is offline and reproducible. Provenance,
versions and licences are in vendor/editeur/SOURCE.md and
vendor/transpect/2.1/SOURCE.md.
- ONIX 3.0 / 3.1 — EDItEUR XSDs, code lists Issue 74.
- ONIX 2.1 — EDItEUR withdrew the 2.1 XSDs, so the 2.1 registry is generated from the RelaxNG conversion published by transpect.io (BSD-2-Clause), which carries ONIX 2.1 revision 03 verbatim.
ONIX is a standard of EDItEUR. Its schemas and code lists are copyright EDItEUR and are redistributed here unmodified, as permitted for general use.
dev_docs/ carries the plan, a per-phase write-up, and a decision log
(decisions.md) recording the seventeen architectural decisions
and why each was made — including the ones that turned out to be wrong.
Two worth knowing if you touch the conversion code:
- ONIX 3.1 is not a superset of 3.0. It removes 20 elements and adds 18, so every version change is potentially lossy, including 3.0 → 3.1.
- Never rename an element the target version already defines. The reverse rename table
is derived by inverting the forward one, and that inversion is lossy — five 2.1 names
collapse onto
TitleText. Ignoring this silently lost the book's title on a 2.1 → 3.0 → 2.1 round trip.
SvelteKit (adapter-static, SPA) · Svelte 5 runes · TypeScript · Vitest ·
fast-xml-parser · nginx.
MIT.