# `uri/format-uri` ## Purpose Build a canonical `otpauth://` URI from an `OtpUri` value object. Round-trips with `parseUri` for every valid input. Percent-encodes the label so colons and spaces survive transport; rejects literal `:` in the label (the colon is reserved for the issuer-prefix convention and an unencoded `:` would produce an ambiguous URI). ## Features - TOTP and HOTP variants. - Label and issuer percent-encoded. - Query parameters emitted in a stable order (`secret, issuer, algorithm, digits, period | counter`). - Strict-mode Zod boundary at the entry point. ## Usage: Basic Example ```ts import { formatUri } from "@suaveplan/otp/uri"; const url = formatUri({ type: "totp", label: "alice@example.com", secret: "JBSWY3DPEHPK3PXP", issuer: "Example", }); ``` ## Usage: Advanced Example ```ts import { formatUri } from "@suaveplan/otp/uri"; const url = formatUri({ type: "hotp", label: "alice", secret: "JBSWY3DPEHPK3PXP", counter: 17n, algorithm: "SHA256", digits: 8, }); ``` ## API Reference - `formatUri(opts: OtpUri): string` — returns the URI. Raises `OtpError(OTP.URI_MALFORMED)` on Zod failure or on labels containing `:`. ## Implementation Notes The output is a stable canonical form so two calls with identically-typed input produce byte-identical URLs. This matters when the URI is hashed for QR-code de-duplication or audited via diff tooling. Field order is preserved via a `Map` rather than a plain object so insertion order is stable across JS engines. Optional fields are omitted when they match the per-spec default — e.g. TOTP URIs without a `period` parameter elide it rather than emitting `period=30`. This keeps the output minimal and matches the wire format emitted by reference Google Authenticator implementations. Round-tripping through `parseUri` recovers the canonical option bag because both ends agree on the defaults; consumers that need an explicit `period=30` should set it on the input.