@aoxborrow/registrar-client 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -599,8 +599,8 @@ var HttpClient = class {
599
599
  case 404:
600
600
  return new NotFoundError(message);
601
601
  case 429: {
602
- const retryAfter = Number(response.headers.get("retry-after"));
603
- return new RateLimitError(message, Number.isFinite(retryAfter) ? retryAfter : void 0);
602
+ const retryAfter = parseRetryAfter(response.headers.get("retry-after")) ?? this.config.rateLimitFallbackSeconds;
603
+ return new RateLimitError(message, retryAfter);
604
604
  }
605
605
  default: {
606
606
  if (response.status >= 500) {
@@ -613,6 +613,19 @@ var HttpClient = class {
613
613
  }
614
614
  }
615
615
  };
616
+ function parseRetryAfter(value) {
617
+ const text2 = value?.trim();
618
+ if (!text2) return void 0;
619
+ if (/^\d+$/.test(text2)) {
620
+ const seconds = Number(text2);
621
+ return Number.isSafeInteger(seconds) ? seconds : void 0;
622
+ }
623
+ const zonedDate = /^(?:[a-z]{3}, \d{2} [a-z]{3} \d{4}|[a-z]+, \d{2}-[a-z]{3}-\d{2}) \d{2}:\d{2}:\d{2} GMT$/i;
624
+ const asctimeDate = /^[a-z]{3} [a-z]{3} (?: \d|\d{2}) \d{2}:\d{2}:\d{2} \d{4}$/i;
625
+ if (!zonedDate.test(text2) && !asctimeDate.test(text2)) return void 0;
626
+ const date = Date.parse(asctimeDate.test(text2) ? `${text2} GMT` : text2);
627
+ return Number.isFinite(date) ? Math.max(0, Math.ceil((date - Date.now()) / 1e3)) : void 0;
628
+ }
616
629
  function isOutcomeUnknown(error) {
617
630
  if (error instanceof TimeoutError || error instanceof InvalidResponseError) return true;
618
631
  return error instanceof ConnectionError && !error.notSent;
@@ -1065,8 +1078,8 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
1065
1078
  message: `${domainName} is not a zone in this account; DNS is managed via the Zones API`
1066
1079
  };
1067
1080
  }
1068
- const dnsPath = `/zones/${encodeURIComponent(zone.id)}/dns_records`;
1069
- const list = await this.http.request({ path: dnsPath, ...opts });
1081
+ const dnsPath2 = `/zones/${encodeURIComponent(zone.id)}/dns_records`;
1082
+ const list = await this.http.request({ path: dnsPath2, ...opts });
1070
1083
  if (!list.success) {
1071
1084
  return {
1072
1085
  success: false,
@@ -1077,7 +1090,7 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
1077
1090
  if (!rec.id) continue;
1078
1091
  const del = await this.http.request({
1079
1092
  method: "DELETE",
1080
- path: `${dnsPath}/${encodeURIComponent(rec.id)}`,
1093
+ path: `${dnsPath2}/${encodeURIComponent(rec.id)}`,
1081
1094
  ...opts
1082
1095
  });
1083
1096
  if (!del.success) {
@@ -1093,7 +1106,7 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
1093
1106
  if (type === "NS" && name === zone.name) continue;
1094
1107
  const create = await this.http.request({
1095
1108
  method: "POST",
1096
- path: dnsPath,
1109
+ path: dnsPath2,
1097
1110
  body: toCfDnsBody(record, type, name),
1098
1111
  ...opts
1099
1112
  });
@@ -1762,6 +1775,527 @@ function toContact(c) {
1762
1775
  return contact;
1763
1776
  }
1764
1777
 
1778
+ // src/registrars/domain101.ts
1779
+ var Domain101HttpClient = class extends HttpClient {
1780
+ async request(req) {
1781
+ try {
1782
+ return await super.request(req);
1783
+ } catch (error) {
1784
+ if (error instanceof AuthorizationError) {
1785
+ const scope = requiredScope(req);
1786
+ if (!scope || error.providerCode !== "INSUFFICIENT_PERMISSIONS") throw error;
1787
+ const scoped = new AuthorizationError(
1788
+ `101domain: this API key does not have the ${scope} scope. Create a key with ${scope} to ${SCOPE_PURPOSE[scope]}.`
1789
+ );
1790
+ scoped.providerCode = error.providerCode;
1791
+ throw scoped;
1792
+ }
1793
+ if (error instanceof OutcomeUnknownError)
1794
+ throw new OutcomeUnknownError(
1795
+ "101domain: the registrar did not confirm this change. Re-read the domain before trying again.",
1796
+ { feature: error.feature }
1797
+ );
1798
+ if (error instanceof ParsingError) throw new ParsingError("101domain: invalid JSON response");
1799
+ if (error instanceof ConnectionError)
1800
+ throw new ConnectionError("101domain: network request failed", { notSent: error.notSent });
1801
+ throw error;
1802
+ }
1803
+ }
1804
+ async toStatusError(response, url) {
1805
+ const detail = await errorDetail(response);
1806
+ const error = await super.toStatusError(
1807
+ new Response(null, { status: response.status, headers: response.headers }),
1808
+ url
1809
+ );
1810
+ if (detail) {
1811
+ error.message = `101domain: ${detail.message}`;
1812
+ error.providerCode = detail.code;
1813
+ } else if (response.status === 403) {
1814
+ error.message = "101domain: the request was refused before reaching the API (403 without an API error)";
1815
+ }
1816
+ return error;
1817
+ }
1818
+ };
1819
+ var SCOPE_PURPOSE = {
1820
+ domains_read: "read the domain portfolio",
1821
+ domains_write: "change URL forwarding",
1822
+ dns_read: "read DNS records and nameservers",
1823
+ dns_write: "change DNS records and nameservers"
1824
+ };
1825
+ function requiredScope(req) {
1826
+ const write = !REST_SAFE_METHODS.includes((req.method ?? "GET").toUpperCase());
1827
+ if (req.path.startsWith("/dns/")) return write ? "dns_write" : "dns_read";
1828
+ if (req.path === "/domains" || /^\/domains\/[^/]+(?:\/forwarding)?$/.test(req.path))
1829
+ return write ? "domains_write" : "domains_read";
1830
+ return void 0;
1831
+ }
1832
+ async function errorDetail(response) {
1833
+ try {
1834
+ const body = await response.json();
1835
+ if (typeof body?.code !== "string" || typeof body.message !== "string" || !body.message)
1836
+ return null;
1837
+ return { code: body.code, message: body.message.replace(/\s+/g, " ").slice(0, 200) };
1838
+ } catch {
1839
+ return null;
1840
+ }
1841
+ }
1842
+ var Domain101Registrar = class extends BaseRegistrar {
1843
+ name = "101domain";
1844
+ static safeMethods = REST_SAFE_METHODS;
1845
+ static displayName = "101domain";
1846
+ static website = "101domain.com";
1847
+ static supportsSandbox = false;
1848
+ static configFields = [
1849
+ { name: "apiKey", label: "API key", type: "password", required: true }
1850
+ ];
1851
+ static helpText = "Create a Bearer API key under My Account > Developer Tools - API & MCP. Use domains_read and dns_read for reads; dns_write for DNS/nameservers and domains_write for forwarding. Only a primary account with 2FA or SSO can create keys. Keys expire after at most one year. Registration, renewal, auto-renew, privacy, lock, contacts and transfers are not available through this API.";
1852
+ static extendedFeatures = [
1853
+ Feature.GetDomainForwarding,
1854
+ Feature.SetDomainForwarding
1855
+ ];
1856
+ constructor(credentials, options) {
1857
+ const apiKey = credentials.apiKey?.trim() ?? "";
1858
+ if (/[\r\n]/.test(apiKey)) throw new ConfigurationError("101domain: invalid API key format");
1859
+ const config = {
1860
+ baseUrl: selectBaseUrl("101domain", options?.environment, {
1861
+ production: "https://api.101domain.com/v1"
1862
+ }),
1863
+ headers: { Authorization: `Bearer ${apiKey}` }
1864
+ };
1865
+ super(credentials, config, options);
1866
+ this.http = new Domain101HttpClient({
1867
+ ...config,
1868
+ safeMethods: REST_SAFE_METHODS,
1869
+ options: this.options
1870
+ });
1871
+ }
1872
+ async testConnection(opts) {
1873
+ try {
1874
+ await this.request({
1875
+ path: "/domains",
1876
+ query: { page: 1, per_page: 1 },
1877
+ ...opts
1878
+ });
1879
+ return { success: true, message: "Connection successful" };
1880
+ } catch (error) {
1881
+ return { success: false, message: toRegistrarError(error).message };
1882
+ }
1883
+ }
1884
+ async listDomains(opts) {
1885
+ const { search, detailed: _detailed, ...requestOpts } = opts ?? {};
1886
+ const result = [];
1887
+ const seen = /* @__PURE__ */ new Set();
1888
+ for (let page = 1; ; page++) {
1889
+ const response = await this.request({
1890
+ path: "/domains",
1891
+ query: { page, per_page: 50 },
1892
+ ...requestOpts
1893
+ });
1894
+ if (!Array.isArray(response.data)) throw invalid("domain list");
1895
+ const pagination = response.meta?.pagination;
1896
+ if (!pagination || pagination.current_page !== page || !Number.isInteger(pagination.total_pages) || pagination.total_pages < 0 || pagination.total_pages > 1e5 || !Number.isInteger(pagination.total) || pagination.total < 0)
1897
+ throw invalid("pagination");
1898
+ for (const raw of response.data) {
1899
+ const domain = toDomain(raw);
1900
+ if (seen.has(domain.domainName)) throw invalid("duplicate domain across pages");
1901
+ seen.add(domain.domainName);
1902
+ result.push(domain);
1903
+ }
1904
+ if (page >= pagination.total_pages) {
1905
+ if (result.length !== pagination.total) throw invalid("incomplete portfolio");
1906
+ return filterDomains(
1907
+ result.filter((d) => !d.deleted),
1908
+ search
1909
+ );
1910
+ }
1911
+ if (!response.data.length) throw invalid("empty intermediate page");
1912
+ }
1913
+ }
1914
+ async getDomain(domainName, opts) {
1915
+ const domain = toDomain(
1916
+ (await this.request({ path: domainPath(domainName), ...opts })).data
1917
+ );
1918
+ if (domain.domainName !== normalizeDomain(domainName)) throw invalid("domain identity");
1919
+ return domain;
1920
+ }
1921
+ async getNameservers(domainName, opts) {
1922
+ const raw = (await this.request({ path: `${dnsPath(domainName)}/nameservers`, ...opts })).data;
1923
+ if (!Array.isArray(raw) || raw.some((n) => typeof n !== "string")) throw invalid("nameservers");
1924
+ return raw.map((n) => n.toLowerCase());
1925
+ }
1926
+ async updateNameservers(domainName, nameservers, opts) {
1927
+ const normalized = nameservers.map((n) => n.trim().toLowerCase());
1928
+ if (normalized.length < 2 || normalized.length > 13 || new Set(normalized).size !== normalized.length || normalized.some((n) => !/^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9-]+$/.test(n)))
1929
+ throw new ConfigurationError("101domain: provide 2\u201313 distinct nameserver hostnames");
1930
+ try {
1931
+ const response = await this.request({
1932
+ ...opts,
1933
+ method: "PUT",
1934
+ path: `${dnsPath(domainName)}/nameservers`,
1935
+ body: { nameservers: normalized }
1936
+ });
1937
+ if (response.data?.change_status === "pending")
1938
+ return {
1939
+ success: true,
1940
+ pending: true,
1941
+ message: "Nameserver change submitted; pending registry processing. Re-read nameservers to confirm completion."
1942
+ };
1943
+ if (response.data?.change_status === "failed")
1944
+ return { success: false, message: "101domain rejected the nameserver change" };
1945
+ if (response.data?.change_status !== "completed")
1946
+ throw new OutcomeUnknownError(
1947
+ "101domain: invalid nameserver change confirmation. Re-read nameservers before trying again."
1948
+ );
1949
+ return { success: true, message: "Nameservers already configured as requested" };
1950
+ } catch (error) {
1951
+ return failed(error);
1952
+ }
1953
+ }
1954
+ async checkAvailability(domainNames, opts) {
1955
+ const names = [...new Set(domainNames.map(normalizeDomain))];
1956
+ const result = [];
1957
+ for (const domains of chunks(names, 50)) {
1958
+ const response = await this.request({
1959
+ ...opts,
1960
+ method: "POST",
1961
+ path: "/domains/bulk-search",
1962
+ body: { domains, pricing_term: 1 }
1963
+ });
1964
+ if (!Array.isArray(response.data) || (response.invalid?.length ?? 0) > 0)
1965
+ throw invalid("availability results");
1966
+ const found = /* @__PURE__ */ new Set();
1967
+ for (const raw of response.data) {
1968
+ if (typeof raw.domain_name !== "string" || typeof raw.available !== "boolean")
1969
+ throw invalid("availability result");
1970
+ const domainName = normalizeDomain(raw.domain_name);
1971
+ if (!domains.includes(domainName) || found.has(domainName))
1972
+ throw invalid("availability identity");
1973
+ found.add(domainName);
1974
+ const price2 = oneYear(raw.pricing);
1975
+ result.push({
1976
+ domainName,
1977
+ available: raw.available,
1978
+ premium: price2?.premium,
1979
+ price: amount(price2?.register),
1980
+ renewalPrice: amount(price2?.renew),
1981
+ currency: price2?.currency,
1982
+ period: 1
1983
+ });
1984
+ }
1985
+ if (found.size !== domains.length) throw invalid("incomplete availability results");
1986
+ }
1987
+ return result;
1988
+ }
1989
+ async getPricing(tldOrDomain, opts) {
1990
+ const name = normalizeDomain(tldOrDomain);
1991
+ if (name.startsWith(".") || !name.includes(".")) {
1992
+ const tld = name.replace(/^\./, "");
1993
+ const response2 = await this.request({
1994
+ path: `/tlds/${encodeURIComponent("." + tld)}`,
1995
+ query: { pricing_terms: "1" },
1996
+ ...opts
1997
+ });
1998
+ const price3 = oneYear(response2.data?.pricing);
1999
+ if (!price3) throw new NotFoundError("101domain: no one-year pricing for this TLD");
2000
+ return {
2001
+ tld,
2002
+ currency: price3.currency,
2003
+ registration: amount(price3.register),
2004
+ renewal: amount(price3.renew),
2005
+ transfer: amount(price3.transfer)
2006
+ };
2007
+ }
2008
+ const response = await this.request({
2009
+ path: "/domains/search",
2010
+ query: { domain_name: name, pricing_terms: "1" },
2011
+ ...opts
2012
+ });
2013
+ const raw = response.data;
2014
+ if (!raw || normalizeDomain(raw.domain_name) !== name) throw invalid("pricing identity");
2015
+ const price2 = oneYear(raw.pricing);
2016
+ if (!price2) throw new NotFoundError("101domain: pricing unavailable for this domain");
2017
+ return {
2018
+ tld: name.slice(name.indexOf(".") + 1),
2019
+ currency: price2.currency,
2020
+ registration: amount(price2.register),
2021
+ renewal: amount(price2.renew),
2022
+ transfer: amount(price2.transfer)
2023
+ };
2024
+ }
2025
+ async getDnsRecords(domainName, opts) {
2026
+ return (await this.rawRecords(domainName, opts)).map(fromRecord);
2027
+ }
2028
+ // Reconcile by content-derived IDs. Preserve unchanged records, edit by ID, create additions
2029
+ // before removing stale records, and stop on any failed batch. Never retry an
2030
+ // uncertain write. This replaces the custom records, not the apex NS delegation.
2031
+ async setDnsRecords(domainName, records, opts) {
2032
+ const desired = records.map((r) => recordSpec(r, domainName));
2033
+ const unique = desired.filter(
2034
+ (r, i) => desired.findIndex((other) => recordSignature(other) === recordSignature(r)) === i
2035
+ );
2036
+ let changed = false;
2037
+ try {
2038
+ const stale = (await this.rawRecords(domainName, opts)).map((raw) => ({ raw, spec: recordSpec(fromRecord(raw), domainName, false) })).filter(({ spec }) => !(spec.type === "NS" && spec.name === ""));
2039
+ const additions = [];
2040
+ const edits = [];
2041
+ for (const record of unique) {
2042
+ const index = stale.findIndex(
2043
+ ({ spec }) => recordSignature(spec) === recordSignature(record)
2044
+ );
2045
+ if (index >= 0) stale.splice(index, 1);
2046
+ else {
2047
+ const priorIndex = stale.findIndex(
2048
+ ({ spec }) => spec.type === record.type && spec.name === record.name
2049
+ );
2050
+ const prior = priorIndex >= 0 ? stale.splice(priorIndex, 1)[0].raw : void 0;
2051
+ if (prior?.proxied) record.proxied = true;
2052
+ if (record.ttl === 1 && record.proxied !== true)
2053
+ throw new ConfigurationError(
2054
+ "101domain: automatic TTL requires an existing proxied SWA record"
2055
+ );
2056
+ if (prior) {
2057
+ const edit = { ...record, id: prior.id };
2058
+ delete edit.name;
2059
+ delete edit.type;
2060
+ edits.push(edit);
2061
+ } else additions.push(record);
2062
+ }
2063
+ }
2064
+ for (const batch of chunks(edits, 25)) {
2065
+ const response = await this.request({
2066
+ ...opts,
2067
+ method: "PATCH",
2068
+ path: `${dnsPath(domainName)}/records`,
2069
+ body: { records: batch }
2070
+ });
2071
+ if (!Array.isArray(response.data) || response.data.length !== batch.length || new Set(response.data.map((r) => r?.old_id)).size !== batch.length || response.data.some(
2072
+ (r) => !r || !batch.some((edit) => edit.id === r.old_id) || typeof r.id !== "string" || !r.id
2073
+ ))
2074
+ throw new OutcomeUnknownError(
2075
+ "101domain: invalid DNS edit confirmation. Re-read the zone before trying again."
2076
+ );
2077
+ changed = true;
2078
+ }
2079
+ for (const batch of chunks(additions, 25)) {
2080
+ await this.request({
2081
+ ...opts,
2082
+ method: "POST",
2083
+ path: `${dnsPath(domainName)}/records`,
2084
+ body: { records: batch }
2085
+ });
2086
+ changed = true;
2087
+ }
2088
+ for (const batch of chunks(stale, 25)) {
2089
+ await this.request({
2090
+ ...opts,
2091
+ method: "DELETE",
2092
+ path: `${dnsPath(domainName)}/records`,
2093
+ body: { ids: batch.map(({ raw }) => raw.id) }
2094
+ });
2095
+ changed = true;
2096
+ }
2097
+ return { success: true, message: "DNS records updated successfully" };
2098
+ } catch (error) {
2099
+ const result = failed(error);
2100
+ if (!changed && !result.outcome) return result;
2101
+ return {
2102
+ ...result,
2103
+ message: `${result.message}. DNS replacement is not atomic; read the zone before retrying.`
2104
+ };
2105
+ }
2106
+ }
2107
+ async getDomainForwarding(domainName, opts) {
2108
+ const raw = (await this.request({
2109
+ path: domainPath(domainName),
2110
+ ...opts
2111
+ })).data;
2112
+ if (!raw || normalizeDomain(raw.domain_name) !== normalizeDomain(domainName))
2113
+ throw invalid("domain identity");
2114
+ const forward = raw.web_forwarding;
2115
+ if (forward === null || forward?.destination === null && forward.type === null) return [];
2116
+ if (!forward || typeof forward.destination !== "string" || forward.type !== "301" && forward.type !== "cloak")
2117
+ throw invalid("forwarding");
2118
+ return [
2119
+ {
2120
+ host: "@",
2121
+ url: forward.destination,
2122
+ type: forward.type === "301" ? "permanent" : "masked"
2123
+ }
2124
+ ];
2125
+ }
2126
+ async setDomainForwarding(domainName, forwards, opts) {
2127
+ if (forwards.length > 1 || forwards.some((f) => f.host !== "@" || f.type !== "permanent"))
2128
+ throw new ConfigurationError(
2129
+ "101domain: only one permanent apex forwarding rule is supported"
2130
+ );
2131
+ if (forwards.length) {
2132
+ let url;
2133
+ try {
2134
+ url = new URL(forwards[0].url);
2135
+ } catch {
2136
+ throw new ConfigurationError("101domain: forwarding needs a valid HTTP(S) URL");
2137
+ }
2138
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password)
2139
+ throw new ConfigurationError("101domain: forwarding needs a valid HTTP(S) URL");
2140
+ }
2141
+ try {
2142
+ const existing = await this.getDomainForwarding(domainName, opts);
2143
+ const path = `${domainPath(domainName)}/forwarding`;
2144
+ if (!forwards.length) {
2145
+ if (existing.length) await this.request({ ...opts, method: "DELETE", path });
2146
+ } else if (existing[0]?.url !== forwards[0].url || existing[0]?.type !== "permanent") {
2147
+ await this.request({
2148
+ ...opts,
2149
+ method: existing.length ? "PATCH" : "POST",
2150
+ path,
2151
+ body: { destination: forwards[0].url, type: "301" }
2152
+ });
2153
+ }
2154
+ return {
2155
+ success: true,
2156
+ message: forwards.length ? "URL forwarding updated" : "URL forwarding cleared"
2157
+ };
2158
+ } catch (error) {
2159
+ return failed(error);
2160
+ }
2161
+ }
2162
+ async request(req) {
2163
+ const raw = await this.http.request(req);
2164
+ if (!raw || typeof raw !== "object" || raw.status !== "success" || !("data" in raw)) {
2165
+ if (req.call?.intent === "write" && !REST_SAFE_METHODS.includes(req.method ?? "GET"))
2166
+ throw new OutcomeUnknownError(
2167
+ "101domain: invalid write confirmation. Re-read the domain before trying again."
2168
+ );
2169
+ throw invalid("response envelope");
2170
+ }
2171
+ return raw;
2172
+ }
2173
+ async rawRecords(domainName, opts) {
2174
+ const raw = (await this.request({ path: `${dnsPath(domainName)}/records`, ...opts })).data;
2175
+ if (!Array.isArray(raw) || raw.some(
2176
+ (r) => !r || typeof r.id !== "string" || !r.id || typeof r.name !== "string" || typeof r.value !== "string" || typeof r.type !== "string" || !Number.isInteger(r.ttl)
2177
+ ))
2178
+ throw invalid("DNS records");
2179
+ if (new Set(raw.map((r) => r.id)).size !== raw.length) throw invalid("duplicate DNS ids");
2180
+ return raw;
2181
+ }
2182
+ };
2183
+ function invalid(part) {
2184
+ return new InvalidResponseError(`101domain: invalid ${part}`);
2185
+ }
2186
+ function failed(error) {
2187
+ return {
2188
+ success: false,
2189
+ message: toRegistrarError(error).message,
2190
+ ...error instanceof OutcomeUnknownError ? { outcome: "unknown" } : {}
2191
+ };
2192
+ }
2193
+ function domainPath(name) {
2194
+ return `/domains/${encodeURIComponent(normalizeDomain(name))}`;
2195
+ }
2196
+ function dnsPath(name) {
2197
+ return `/dns/${encodeURIComponent(normalizeDomain(name))}`;
2198
+ }
2199
+ function chunks(items, size) {
2200
+ const result = [];
2201
+ for (let i = 0; i < items.length; i += size) result.push(items.slice(i, i + size));
2202
+ return result;
2203
+ }
2204
+ function oneYear(pricing) {
2205
+ if (pricing == null) return void 0;
2206
+ if (!Array.isArray(pricing)) throw invalid("pricing");
2207
+ return pricing.find((p) => p?.term_years === 1);
2208
+ }
2209
+ function amount(raw) {
2210
+ if (raw == null) return void 0;
2211
+ if (!/^\d+(?:\.\d+)?$/.test(raw)) throw invalid("price");
2212
+ const value = Number(raw);
2213
+ if (!Number.isFinite(value)) throw invalid("price");
2214
+ return value;
2215
+ }
2216
+ function toDomain(raw) {
2217
+ if (!raw || typeof raw.domain_name !== "string" || !raw.domain_name.trim())
2218
+ throw invalid("domain");
2219
+ return createDomain({
2220
+ domainName: normalizeDomain(raw.domain_name),
2221
+ registrar: "101domain",
2222
+ status: raw.status,
2223
+ createdDate: raw.registered_at,
2224
+ expirationDate: raw.expires_at,
2225
+ autoRenew: raw.auto_renew,
2226
+ locked: raw.registry_statuses?.some((s) => /^clientTransferProhibited$/i.test(s)),
2227
+ nameservers: raw.nameservers,
2228
+ deleted: !isHeld(raw)
2229
+ });
2230
+ }
2231
+ var GONE = /* @__PURE__ */ new Set(["DELETED", "CANCELLED", "DENIED", "XFER_AWAY"]);
2232
+ function isHeld(raw) {
2233
+ const status = raw.status?.toUpperCase() ?? "";
2234
+ return !GONE.has(status) && !status.startsWith("XFER_IN_") && raw.registered_at != null;
2235
+ }
2236
+ function fromRecord(raw) {
2237
+ const common = { name: raw.name || "@", type: raw.type, ttl: raw.ttl };
2238
+ if (raw.type === "MX") {
2239
+ const match = /^(\d+)\s+(.+)$/.exec(raw.value);
2240
+ if (!match) throw invalid("MX record");
2241
+ return { ...common, value: match[2], priority: Number(match[1]) };
2242
+ }
2243
+ if (raw.type === "SRV") {
2244
+ const match = /^(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/.exec(raw.value);
2245
+ if (!match) throw invalid("SRV record");
2246
+ return {
2247
+ ...common,
2248
+ value: match[4],
2249
+ priority: Number(match[1]),
2250
+ weight: Number(match[2]),
2251
+ port: Number(match[3])
2252
+ };
2253
+ }
2254
+ return { ...common, value: raw.value };
2255
+ }
2256
+ function recordSpec(record, domain, strict = true) {
2257
+ const type = record.type.toUpperCase();
2258
+ const zone = normalizeDomain(domain);
2259
+ let name = record.name.trim().toLowerCase().replace(/\.$/, "");
2260
+ if (name === zone || name === "@") name = "";
2261
+ else if (name.endsWith("." + zone)) name = name.slice(0, -zone.length - 1);
2262
+ const reject = (message) => {
2263
+ if (strict) throw new ConfigurationError(`101domain: ${message}`);
2264
+ };
2265
+ if (!["A", "AAAA", "CNAME", "NS", "MX", "TXT", "SRV", "CAA"].includes(type) || type === "NS" && !name)
2266
+ reject("unsupported DNS type or apex NS record");
2267
+ const ttl = record.ttl ?? 3600;
2268
+ if (!Number.isInteger(ttl) || ttl !== 1 && ttl < 300)
2269
+ reject("DNS TTL must be at least 300 seconds");
2270
+ const result = { name, type, ttl };
2271
+ if (type === "MX" || type === "SRV") {
2272
+ if (!Number.isInteger(record.priority) || record.priority < 0 || record.priority > 65535)
2273
+ reject("MX/SRV priority is required");
2274
+ result.priority = record.priority;
2275
+ result.target = record.value;
2276
+ if (type === "SRV") {
2277
+ if (![record.weight, record.port].every((n) => Number.isInteger(n) && n >= 0 && n <= 65535))
2278
+ reject("SRV weight and port are required");
2279
+ result.weight = record.weight;
2280
+ result.port = record.port;
2281
+ }
2282
+ } else if (type === "CAA") {
2283
+ const match = /^(\d+)\s+(\w+)\s+"([\s\S]*)"$/.exec(record.value);
2284
+ if (!match || Number(match[1]) > 255) {
2285
+ reject("CAA needs flags, tag and quoted value");
2286
+ result.value = record.value;
2287
+ } else {
2288
+ result.flag = Number(match[1]);
2289
+ result.tag = match[2];
2290
+ result.value = match[3];
2291
+ }
2292
+ } else result.value = record.value;
2293
+ return result;
2294
+ }
2295
+ function recordSignature(record) {
2296
+ return JSON.stringify(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
2297
+ }
2298
+
1765
2299
  // src/registrars/dynadot.ts
1766
2300
  var DYNADOT_DEFAULT_NS = ["ns1.dynadot.com", "ns2.dynadot.com"];
1767
2301
  var SEARCH_BATCH_SIZE = 100;
@@ -1800,7 +2334,10 @@ var DynadotRegistrar = class extends BaseRegistrar {
1800
2334
  production: "https://api.dynadot.com",
1801
2335
  sandbox: "https://api-sandbox.dynadot.com"
1802
2336
  }),
1803
- headers: { Authorization: `Bearer ${credentials.apiKey}` }
2337
+ headers: { Authorization: `Bearer ${credentials.apiKey}` },
2338
+ // HTTP 429 can omit Retry-After. Avoid exhausting all attempts inside
2339
+ // Dynadot's regular-tier minute window in that case.
2340
+ rateLimitFallbackSeconds: 60
1804
2341
  },
1805
2342
  options
1806
2343
  );
@@ -2436,12 +2973,12 @@ function isPrivacyOn(privacy) {
2436
2973
  }
2437
2974
  function oneYearPrice(prices) {
2438
2975
  if (!prices?.length) return void 0;
2439
- const oneYear = prices.find((p) => /\b1\s*year\b/i.test(p.unit ?? "")) ?? prices[0];
2976
+ const oneYear2 = prices.find((p) => /\b1\s*year\b/i.test(p.unit ?? "")) ?? prices[0];
2440
2977
  return {
2441
- currency: oneYear.currency,
2442
- registration: toNumber(oneYear.registration_price),
2443
- renewal: toNumber(oneYear.renewal_price),
2444
- transfer: toNumber(oneYear.transfer_price)
2978
+ currency: oneYear2.currency,
2979
+ registration: toNumber(oneYear2.registration_price),
2980
+ renewal: toNumber(oneYear2.renewal_price),
2981
+ transfer: toNumber(oneYear2.transfer_price)
2445
2982
  };
2446
2983
  }
2447
2984
  function extractNameservers(glue) {
@@ -2731,8 +3268,8 @@ var GandiRegistrar = class extends BaseRegistrar {
2731
3268
  available: status.startsWith("available"),
2732
3269
  premium: status.includes("premium") || price2?.type === "premium"
2733
3270
  };
2734
- const amount = pickPrice(price2);
2735
- if (amount != null) result.price = amount;
3271
+ const amount2 = pickPrice(price2);
3272
+ if (amount2 != null) result.price = amount2;
2736
3273
  if (resp.currency) result.currency = resp.currency;
2737
3274
  if (price2?.duration_unit === "y" && price2.min_duration != null) {
2738
3275
  result.period = price2.min_duration;
@@ -4741,9 +5278,10 @@ var NamecheapRegistrar = class extends BaseRegistrar {
4741
5278
  }
4742
5279
  /**
4743
5280
  * Availability via domains.check (batched at 50 per request, Namecheap's cap).
4744
- * The check response only carries a price for *premium* names
4745
- * (`PremiumRegistrationPrice`); regular registration pricing comes from
4746
- * `getPricing`, so `price` is set only for premium results.
5281
+ * The check response only carries prices for *premium* names
5282
+ * (`PremiumRegistrationPrice`, `PremiumRenewalPrice`); regular pricing comes
5283
+ * from `getPricing`, so `price` and `renewalPrice` are set only for premium
5284
+ * results.
4747
5285
  */
4748
5286
  async checkAvailability(domainNames, opts) {
4749
5287
  const results = [];
@@ -4756,12 +5294,16 @@ var NamecheapRegistrar = class extends BaseRegistrar {
4756
5294
  );
4757
5295
  for (const el of ensureArray(cr.DomainCheckResult)) {
4758
5296
  const premium = el["@_IsPremiumName"] === "true";
4759
- const premiumPrice = premium ? Number(el["@_PremiumRegistrationPrice"]) : NaN;
5297
+ const premiumPrice = (attr) => {
5298
+ const n = premium ? Number(attr) : NaN;
5299
+ return Number.isFinite(n) && n > 0 ? n : void 0;
5300
+ };
4760
5301
  results.push({
4761
5302
  domainName: el["@_Domain"] ?? "",
4762
5303
  available: el["@_Available"] === "true",
4763
5304
  premium,
4764
- price: Number.isFinite(premiumPrice) && premiumPrice > 0 ? premiumPrice : void 0
5305
+ price: premiumPrice(el["@_PremiumRegistrationPrice"]),
5306
+ renewalPrice: premiumPrice(el["@_PremiumRenewalPrice"])
4765
5307
  });
4766
5308
  }
4767
5309
  }
@@ -5169,12 +5711,11 @@ var NamecomHttpClient = class extends HttpClient {
5169
5711
  }),
5170
5712
  url
5171
5713
  );
5172
- if (error instanceof RateLimitError) {
5173
- const retryAfter = response.headers.get("retry-after");
5174
- const seconds = retryAfter == null ? NaN : Number(retryAfter);
5175
- const date = retryAfter == null ? NaN : Date.parse(retryAfter);
5176
- const reset = response.headers.get("x-ratelimit-reset");
5177
- error.retryAfter = Number.isFinite(seconds) ? Math.max(0, seconds) : Number.isFinite(date) ? Math.max(0, (date - Date.now()) / 1e3) : reset != null && Number.isFinite(Number(reset)) ? Math.max(0, Number(reset) - Date.now() / 1e3) : void 0;
5714
+ if (error instanceof RateLimitError && error.retryAfter === void 0) {
5715
+ const reset = response.headers.get("x-ratelimit-reset")?.trim();
5716
+ if (reset && /^\d+(?:\.\d+)?$/.test(reset)) {
5717
+ error.retryAfter = Math.max(0, Math.ceil(Number(reset) - Date.now() / 1e3));
5718
+ }
5178
5719
  }
5179
5720
  return error;
5180
5721
  }
@@ -5241,7 +5782,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
5241
5782
  }
5242
5783
  async getAuthCode(domainName, opts) {
5243
5784
  const result = await this.http.request({
5244
- path: `${domainPath(domainName)}:getAuthCode`,
5785
+ path: `${domainPath2(domainName)}:getAuthCode`,
5245
5786
  ...opts
5246
5787
  });
5247
5788
  if (!result?.authCode) throw new InvalidResponseError("namecom: missing authorization code");
@@ -5336,7 +5877,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
5336
5877
  validYears(years);
5337
5878
  const pricing = await this.domainPricing(domainName, years, opts);
5338
5879
  return this.purchase(
5339
- `${domainPath(domainName)}:renew`,
5880
+ `${domainPath2(domainName)}:renew`,
5340
5881
  {
5341
5882
  years,
5342
5883
  purchasePrice: purchaseQuote(pricing, "renewalPrice")
@@ -5381,7 +5922,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
5381
5922
  }
5382
5923
  updateNameservers(domainName, nameservers, opts) {
5383
5924
  return this.mutate(
5384
- { method: "POST", path: `${domainPath(domainName)}:setNameservers`, body: { nameservers } },
5925
+ { method: "POST", path: `${domainPath2(domainName)}:setNameservers`, body: { nameservers } },
5385
5926
  opts
5386
5927
  );
5387
5928
  }
@@ -5412,14 +5953,14 @@ var NamecomRegistrar = class extends BaseRegistrar {
5412
5953
  return this.mutate(
5413
5954
  {
5414
5955
  method: "POST",
5415
- path: `${domainPath(domainName)}:setContacts`,
5956
+ path: `${domainPath2(domainName)}:setContacts`,
5416
5957
  body: { contacts: toContacts(contacts) }
5417
5958
  },
5418
5959
  opts
5419
5960
  );
5420
5961
  }
5421
5962
  async getDnsRecords(domainName, opts) {
5422
- return (await this.pages(`${domainPath(domainName)}/records`, "records", opts)).map(fromRecord);
5963
+ return (await this.pages(`${domainPath2(domainName)}/records`, "records", opts)).map(fromRecord2);
5423
5964
  }
5424
5965
  // No bulk replace endpoint. Reconcile by id: preserve identical records, update
5425
5966
  // existing type/host pairs, create additions, then delete stale records. This
@@ -5427,7 +5968,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
5427
5968
  async setDnsRecords(domainName, records, opts) {
5428
5969
  const desired = records.map(toRecord).filter((record, index, all) => all.findIndex((other) => sameRecord(other, record)) === index);
5429
5970
  try {
5430
- const path = `${domainPath(domainName)}/records`;
5971
+ const path = `${domainPath2(domainName)}/records`;
5431
5972
  const existing = await this.pages(path, "records", opts);
5432
5973
  if (existing.some((r) => !Number.isInteger(r.id) || r.id <= 0))
5433
5974
  throw new InvalidResponseError("namecom: DNS response missing record ids");
@@ -5464,14 +6005,14 @@ var NamecomRegistrar = class extends BaseRegistrar {
5464
6005
  }
5465
6006
  }
5466
6007
  patchDomain(domainName, body, opts) {
5467
- return this.mutate({ method: "PATCH", path: domainPath(domainName), body }, opts);
6008
+ return this.mutate({ method: "PATCH", path: domainPath2(domainName), body }, opts);
5468
6009
  }
5469
6010
  rawDomain(domainName, opts) {
5470
- return this.http.request({ path: domainPath(domainName), ...opts });
6011
+ return this.http.request({ path: domainPath2(domainName), ...opts });
5471
6012
  }
5472
6013
  domainPricing(domainName, years, opts) {
5473
6014
  return this.http.request({
5474
- path: `${domainPath(domainName)}:getPricing`,
6015
+ path: `${domainPath2(domainName)}:getPricing`,
5475
6016
  query: { years },
5476
6017
  ...opts
5477
6018
  });
@@ -5587,7 +6128,7 @@ function toContacts(contacts) {
5587
6128
  throw new ConfigurationError("namecom: at least one contact is required");
5588
6129
  return result;
5589
6130
  }
5590
- function domainPath(domainName) {
6131
+ function domainPath2(domainName) {
5591
6132
  return `/domains/${encodeURIComponent(normalizeDomain(domainName))}`;
5592
6133
  }
5593
6134
  function validYears(years) {
@@ -5627,7 +6168,7 @@ function toRecord(r) {
5627
6168
  ...["MX", "SRV"].includes(type) ? { priority: r.priority ?? 0 } : {}
5628
6169
  };
5629
6170
  }
5630
- function fromRecord(r) {
6171
+ function fromRecord2(r) {
5631
6172
  const type = r.type.toUpperCase();
5632
6173
  const record = { type, name: r.host || "@", value: r.answer, ttl: r.ttl };
5633
6174
  if (type === "MX" || type === "SRV") record.priority = r.priority ?? 0;
@@ -7448,16 +7989,17 @@ function toSpaceshipContact(c) {
7448
7989
 
7449
7990
  // src/registrars/index.ts
7450
7991
  var registrars = {
7451
- cloudflare: CloudflareRegistrar,
7452
- dynadot: DynadotRegistrar,
7453
- gandi: GandiRegistrar,
7454
- godaddy: GoDaddyRegistrar,
7455
- namebright: NameBrightRegistrar,
7456
- namecheap: NamecheapRegistrar,
7457
- namecom: NamecomRegistrar,
7458
- namesilo: NameSiloRegistrar,
7459
- porkbun: PorkbunRegistrar,
7460
- spaceship: SpaceshipRegistrar
7992
+ "101domain": Domain101Registrar,
7993
+ "cloudflare": CloudflareRegistrar,
7994
+ "dynadot": DynadotRegistrar,
7995
+ "gandi": GandiRegistrar,
7996
+ "godaddy": GoDaddyRegistrar,
7997
+ "namebright": NameBrightRegistrar,
7998
+ "namecheap": NamecheapRegistrar,
7999
+ "namecom": NamecomRegistrar,
8000
+ "namesilo": NameSiloRegistrar,
8001
+ "porkbun": PorkbunRegistrar,
8002
+ "spaceship": SpaceshipRegistrar
7461
8003
  };
7462
8004
  function createRegistrar(name, credentials, options) {
7463
8005
  const RegistrarClass = registrars[name];
@@ -7475,6 +8017,7 @@ exports.ConfigurationError = ConfigurationError;
7475
8017
  exports.ConnectionError = ConnectionError;
7476
8018
  exports.ConsentRequiredError = ConsentRequiredError;
7477
8019
  exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
8020
+ exports.Domain101Registrar = Domain101Registrar;
7478
8021
  exports.DynadotRegistrar = DynadotRegistrar;
7479
8022
  exports.EXTENDED_FEATURES = EXTENDED_FEATURES;
7480
8023
  exports.FEATURE_CALLS = FEATURE_CALLS;