The portable, **provider-abstracted** core that reads and writes DNS records for our farm domains. A small `DnsProvider` interface with a **deSEC** adapter first and Dynadot kept as an optional second backend, so we are never locked to one registrar's API. Every surface in Calling the Module wraps this one core.
## Why Abstract the Provider
DNS APIs differ wildly. Dynadot's is a quirky `command=` call that overwrites the whole zone; deSEC's is clean per-record REST. Rather than bind to either, the module defines an interface — the same idea as Go's `libdns` — and writes a thin adapter per provider:
interface DnsProvider { getRecords(zone): Promise<Rrset[]> upsertRecord(zone, { subname, type, ttl, records }): Promise<void> deleteRecord(zone, subname, type): Promise<void> }
## deSEC First
The deSEC adapter is almost trivial because the API is REST-clean — per-record RRset CRUD, one token:
GET/POST/PATCH/DELETE https://desec.io/api/v1/domains/{zone}/rrsets/ Authorization: Token <desec-token>
No get-everything-then-rewrite, no XML. Automatic DNSSEC comes for free on the deSEC side.
## Two Runtimes, One Core
- A **TypeScript core** (`@hitchhiker/dns`) for The Guide, the CLI, and Node. - A **Rust component on WASM on Spin** — native WebAssembly, the canonical sandboxed service. It exercises Rust→WASM on the Spin server; a TypeScript→WASM variant can be added later to compare the toolchains.
## Secrets
The deSEC token is captured with the keychain-secret discipline and handed to each runtime as an environment variable or a Spin variable — never in the repository, a wiki page, or a chat log. On Spin the outbound sandbox means a leaked component reaches nothing but deSEC.
## See - Farm DNS Automation — why this exists - deSEC Migration — the provider move - Calling the Module — where it runs - WASM on Spin — the Rust adapter