@aoxborrow/registrar-client 0.6.0 → 0.7.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.js CHANGED
@@ -55,11 +55,61 @@ var TimeoutError = class extends RetryableRegistrarError {
55
55
  var ConnectionError = class extends RetryableRegistrarError {
56
56
  status = 503;
57
57
  // Service Unavailable
58
- constructor(message) {
58
+ // true when the failure is known to have happened before the request left:
59
+ // DNS, connection refused, TLS handshake, or a proxy that never opened its
60
+ // tunnel. The registrar cannot have acted on it, so it is safe to re-send
61
+ // even for a write.
62
+ notSent;
63
+ constructor(message, details = {}) {
59
64
  super(message);
60
65
  this.name = "ConnectionError";
66
+ this.notSent = details.notSent ?? false;
61
67
  }
62
68
  };
69
+ var OutcomeUnknownError = class extends RegistrarError {
70
+ status = 504;
71
+ // Gateway Timeout
72
+ feature;
73
+ cause;
74
+ constructor(message, details = {}) {
75
+ super(message);
76
+ this.name = "OutcomeUnknownError";
77
+ this.feature = details.feature;
78
+ this.cause = details.cause;
79
+ }
80
+ };
81
+ var NOT_SENT = /* @__PURE__ */ Symbol.for("registrar-client.notSent");
82
+ function markNotSent(error) {
83
+ if (typeof error === "object" && error !== null) {
84
+ Object.defineProperty(error, NOT_SENT, { value: true, enumerable: false });
85
+ }
86
+ return error;
87
+ }
88
+ var PRE_SEND_CODES = /* @__PURE__ */ new Set([
89
+ "ENOTFOUND",
90
+ "EAI_AGAIN",
91
+ "ECONNREFUSED",
92
+ "EHOSTUNREACH",
93
+ "ENETUNREACH",
94
+ "EADDRNOTAVAIL",
95
+ "UND_ERR_CONNECT_TIMEOUT",
96
+ "CERT_HAS_EXPIRED",
97
+ "CERT_NOT_YET_VALID",
98
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
99
+ "SELF_SIGNED_CERT_IN_CHAIN",
100
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
101
+ "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
102
+ "ERR_TLS_CERT_ALTNAME_INVALID"
103
+ ]);
104
+ function wasNotSent(error) {
105
+ for (let e = error, depth = 0; e && typeof e === "object" && depth < 5; depth++) {
106
+ if (e[NOT_SENT] === true) return true;
107
+ const code = e.code;
108
+ if (typeof code === "string" && PRE_SEND_CODES.has(code)) return true;
109
+ e = e.cause;
110
+ }
111
+ return false;
112
+ }
63
113
  var AuthenticationError = class extends RegistrarError {
64
114
  status = 401;
65
115
  // Unauthorized
@@ -226,6 +276,9 @@ function createDomain(data = {}) {
226
276
  deleted: data.deleted ?? false
227
277
  };
228
278
  }
279
+ function asRead(opts) {
280
+ return { ...opts, call: { ...opts?.call, intent: "read" } };
281
+ }
229
282
 
230
283
  // src/client.ts
231
284
  var RegistrarClient = class {
@@ -376,6 +429,7 @@ var DEFAULT_OPTIONS = {
376
429
  var USER_AGENT = "@aoxborrow/registrar-client";
377
430
 
378
431
  // src/http.ts
432
+ var REST_SAFE_METHODS = ["GET", "HEAD"];
379
433
  var HttpClient = class {
380
434
  constructor(config) {
381
435
  this.config = config;
@@ -399,7 +453,20 @@ var HttpClient = class {
399
453
  async requestText(req) {
400
454
  return this.withRetries(req, () => this.send(req));
401
455
  }
402
- // shared retry-with-backoff wrapper around a single-attempt operation
456
+ // Shared retry-with-backoff wrapper around a single-attempt operation.
457
+ //
458
+ // What may be re-sent depends on where the failure happened and on whether
459
+ // the call reads or writes:
460
+ //
461
+ // failure read write
462
+ // never sent (DNS, refused, TLS, proxy stage) retry retry
463
+ // 429 retry retry
464
+ // unknown (timeout, dropped mid-response, 5xx) retry OutcomeUnknownError
465
+ // any other response fail fail
466
+ //
467
+ // A write whose outcome is unknown may have been applied, so it is never
468
+ // re-sent: a second renewal charges twice, a second record-create leaves a
469
+ // duplicate. The caller re-reads the domain instead.
403
470
  async withRetries(req, attemptFn) {
404
471
  const retries = req.retries ?? this.config.options.retries;
405
472
  const backoff = req.backoff ?? this.config.options.backoff;
@@ -409,6 +476,14 @@ var HttpClient = class {
409
476
  return await attemptFn();
410
477
  } catch (error) {
411
478
  lastError = toRegistrarError(error);
479
+ if (this.intentOf(req) === "write" && isOutcomeUnknown(lastError)) {
480
+ const unknown = new OutcomeUnknownError(
481
+ `The registrar did not confirm this change, so it may or may not have been applied. Check the domain before trying again. (${lastError.message})`,
482
+ { feature: req.call?.feature, cause: lastError }
483
+ );
484
+ if (req.call) req.call.outcomeUnknown = unknown;
485
+ throw unknown;
486
+ }
412
487
  if (attempt >= retries || !lastError.shouldRetry()) {
413
488
  throw lastError;
414
489
  }
@@ -418,6 +493,14 @@ var HttpClient = class {
418
493
  }
419
494
  throw lastError ?? new RegistrarError("Request failed");
420
495
  }
496
+ // Whether a request only reads. A method the API reserves for reads wins;
497
+ // otherwise it is whatever the feature being served is, and a request that
498
+ // says nothing is assumed to write.
499
+ intentOf(req) {
500
+ const method = (req.method ?? "GET").toUpperCase();
501
+ if (this.config.safeMethods?.includes(method)) return "read";
502
+ return req.call?.intent ?? "write";
503
+ }
421
504
  // build the full URL for a request, applying query parameters
422
505
  buildUrl(req) {
423
506
  let base;
@@ -460,7 +543,8 @@ var HttpClient = class {
460
543
  try {
461
544
  let response;
462
545
  try {
463
- response = await fetch(url, {
546
+ const doFetch = req.fetch ?? this.config.options.fetch ?? fetch;
547
+ response = await doFetch(url, {
464
548
  method: req.method ?? "GET",
465
549
  headers,
466
550
  body: req.body !== void 0 ? JSON.stringify(req.body) : void 0,
@@ -471,7 +555,9 @@ var HttpClient = class {
471
555
  if (externalSignal?.aborted) throw new AbortError("Request was aborted");
472
556
  throw new TimeoutError(`Request to '${url}' timed out after ${timeout}ms`);
473
557
  }
474
- throw new ConnectionError(`Failed to reach '${url}': ${errorMessage(error)}`);
558
+ throw new ConnectionError(`Failed to reach '${url}': ${errorMessage(error)}`, {
559
+ notSent: wasNotSent(error)
560
+ });
475
561
  }
476
562
  if (!response.ok) {
477
563
  throw await this.toStatusError(response, url);
@@ -479,7 +565,17 @@ var HttpClient = class {
479
565
  if (response.status === 204) {
480
566
  return "";
481
567
  }
482
- return await response.text();
568
+ try {
569
+ return await response.text();
570
+ } catch (error) {
571
+ if (isAbortError(error)) {
572
+ if (externalSignal?.aborted) throw new AbortError("Request was aborted");
573
+ throw new TimeoutError(`Response from '${url}' timed out after ${timeout}ms`);
574
+ }
575
+ throw new ConnectionError(
576
+ `Connection to '${url}' was lost while reading the response: ${errorMessage(error)}`
577
+ );
578
+ }
483
579
  } finally {
484
580
  clearTimeout(timeoutId);
485
581
  externalSignal?.removeEventListener("abort", onAbort);
@@ -515,6 +611,10 @@ var HttpClient = class {
515
611
  }
516
612
  }
517
613
  };
614
+ function isOutcomeUnknown(error) {
615
+ if (error instanceof TimeoutError || error instanceof InvalidResponseError) return true;
616
+ return error instanceof ConnectionError && !error.notSent;
617
+ }
518
618
  function isAbortError(error) {
519
619
  return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
520
620
  }
@@ -611,6 +711,33 @@ var CORE_FEATURE_SET = new Set(CORE_FEATURES);
611
711
  function isCoreFeature(feature) {
612
712
  return CORE_FEATURE_SET.has(feature);
613
713
  }
714
+ var FEATURE_CALLS = {
715
+ testConnection: { intent: "read", optsIndex: 0 },
716
+ listDomains: { intent: "read", optsIndex: 0 },
717
+ getDomain: { intent: "read", optsIndex: 1 },
718
+ checkAvailability: { intent: "read", optsIndex: 1 },
719
+ getPricing: { intent: "read", optsIndex: 1 },
720
+ registerDomain: { intent: "write", optsIndex: 2 },
721
+ renewDomain: { intent: "write", optsIndex: 2 },
722
+ setAutoRenew: { intent: "write", optsIndex: 2 },
723
+ transferIn: { intent: "write", optsIndex: 2 },
724
+ updateNameservers: { intent: "write", optsIndex: 2 },
725
+ getNameservers: { intent: "read", optsIndex: 1 },
726
+ lockDomain: { intent: "write", optsIndex: 1 },
727
+ unlockDomain: { intent: "write", optsIndex: 1 },
728
+ setPrivacy: { intent: "write", optsIndex: 2 },
729
+ getContacts: { intent: "read", optsIndex: 1 },
730
+ updateContacts: { intent: "write", optsIndex: 2 },
731
+ getDnsRecords: { intent: "read", optsIndex: 1 },
732
+ setDnsRecords: { intent: "write", optsIndex: 2 },
733
+ getAuthCode: { intent: "write", optsIndex: 1 },
734
+ getDnssec: { intent: "read", optsIndex: 1 },
735
+ disableDnssec: { intent: "write", optsIndex: 1 },
736
+ getEmailForwarding: { intent: "read", optsIndex: 1 },
737
+ setEmailForwarding: { intent: "write", optsIndex: 2 },
738
+ getDomainForwarding: { intent: "read", optsIndex: 1 },
739
+ setDomainForwarding: { intent: "write", optsIndex: 2 }
740
+ };
614
741
 
615
742
  // src/registrar.ts
616
743
  function selectBaseUrl(registrar, environment, urls) {
@@ -636,6 +763,9 @@ var BaseRegistrar = class {
636
763
  static get features() {
637
764
  return [...CORE_FEATURES, ...this.extendedFeatures];
638
765
  }
766
+ // HTTP methods this provider's API reserves for reads; see
767
+ // `HttpClientConfig.safeMethods`. REST providers set `REST_SAFE_METHODS`.
768
+ static safeMethods = [];
639
769
  // instance mirror of the static `features`
640
770
  get features() {
641
771
  return this.constructor.features;
@@ -654,7 +784,39 @@ var BaseRegistrar = class {
654
784
  this.credentials = credentials;
655
785
  this.environment = environment;
656
786
  this.options = { ...DEFAULT_OPTIONS, ...clientOptions };
657
- this.http = new HttpClient({ ...httpConfig, options: this.options });
787
+ this.http = new HttpClient({
788
+ safeMethods: this.constructor.safeMethods,
789
+ ...httpConfig,
790
+ options: this.options
791
+ });
792
+ this.threadFeatureCalls();
793
+ }
794
+ // Wrap each feature method so its `RequestOptions` carry a `FeatureCall`:
795
+ // the read/write intent the HTTP layer needs to decide what may be re-sent,
796
+ // and a slot where it reports a write whose outcome is unknown. Providers
797
+ // already forward their options into every request, so nothing else changes.
798
+ //
799
+ // A feature calling another feature (a write re-reading the domain, say)
800
+ // goes through that feature's own wrapper, so the inner read is still a read.
801
+ // Providers fold most failures into an `OperationResult`; the unknown outcome
802
+ // is copied onto that result so it is not lost along with the error.
803
+ threadFeatureCalls() {
804
+ const self = this;
805
+ for (const [feature, { intent, optsIndex }] of Object.entries(FEATURE_CALLS)) {
806
+ const original = self[feature];
807
+ if (typeof original !== "function") continue;
808
+ self[feature] = (...args) => {
809
+ const call = { intent, feature };
810
+ const next = [...args];
811
+ while (next.length <= optsIndex) next.push(void 0);
812
+ next[optsIndex] = { ...next[optsIndex], call };
813
+ const result = original.apply(this, next);
814
+ if (!(result instanceof Promise)) return result;
815
+ return result.then(
816
+ (value) => call.outcomeUnknown && value !== null && typeof value === "object" && value.success === false ? { ...value, outcome: "unknown" } : value
817
+ );
818
+ };
819
+ }
658
820
  }
659
821
  testConnection(_opts) {
660
822
  return Promise.reject(
@@ -745,6 +907,8 @@ var BaseRegistrar = class {
745
907
  // src/registrars/cloudflare.ts
746
908
  var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
747
909
  name = "cloudflare";
910
+ // a REST API: GET and HEAD never change state
911
+ static safeMethods = REST_SAFE_METHODS;
748
912
  static displayName = "Cloudflare";
749
913
  static website = "cloudflare.com";
750
914
  static helpText = 'Create an API token in your Cloudflare dashboard under My Profile > API Tokens. The token needs "Account.Registrar" read/write permissions. Find your Account ID in the URL or on the Overview page of any zone.';
@@ -1602,6 +1766,8 @@ var SEARCH_BATCH_SIZE = 100;
1602
1766
  var SUPPORTED_DNS_TYPES = /* @__PURE__ */ new Set(["A", "AAAA", "CNAME", "MX", "TXT", "NS", "SRV", "CAA"]);
1603
1767
  var DynadotRegistrar = class extends BaseRegistrar {
1604
1768
  name = "dynadot";
1769
+ // a REST API: GET and HEAD never change state
1770
+ static safeMethods = REST_SAFE_METHODS;
1605
1771
  static displayName = "Dynadot";
1606
1772
  static website = "dynadot.com";
1607
1773
  static helpText = "Under Tools > API in your Dynadot account, generate an API Key and API Secret. The account must be unlocked with API access enabled. The key identifies you; the secret signs requests (X-Signature) and is never sent directly.";
@@ -2353,6 +2519,8 @@ var GANDI_TYPE_TO_FORWARD = {
2353
2519
  };
2354
2520
  var GandiRegistrar = class extends BaseRegistrar {
2355
2521
  name = "gandi";
2522
+ // a REST API: GET and HEAD never change state
2523
+ static safeMethods = REST_SAFE_METHODS;
2356
2524
  static displayName = "Gandi.net";
2357
2525
  static website = "gandi.net";
2358
2526
  static helpText = "Generate a Personal Access Token (PAT) in your Gandi account under Account Settings > Security. The token needs permission to manage domains. (Gandi has deprecated the legacy API Key; use a PAT.)";
@@ -3040,6 +3208,8 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3040
3208
  var PRICE_MICRO_UNITS = 1e6;
3041
3209
  var GoDaddyRegistrar = class _GoDaddyRegistrar extends BaseRegistrar {
3042
3210
  name = "godaddy";
3211
+ // a REST API: GET and HEAD never change state
3212
+ static safeMethods = REST_SAFE_METHODS;
3043
3213
  static displayName = "GoDaddy";
3044
3214
  static website = "godaddy.com";
3045
3215
  static helpText = "Create a Personal Access Token (PAT) at https://developer.godaddy.com and paste it here. The PAT is GoDaddy's modern credential: it unlocks the v3 API (discovery, registration, DNS, nameservers) and also authenticates the legacy v1 API for management operations (renew, transfer, auto-renew, lock, privacy, contacts) \u2014 one token covers everything.";
@@ -3920,6 +4090,8 @@ function dnsFieldsDiffer(a, b) {
3920
4090
  var TOKEN_URL = "https://api.namebright.com/auth/token";
3921
4091
  var NameBrightRegistrar = class extends BaseRegistrar {
3922
4092
  name = "namebright";
4093
+ // a REST API: GET and HEAD never change state
4094
+ static safeMethods = REST_SAFE_METHODS;
3923
4095
  static displayName = "NameBright";
3924
4096
  static website = "namebright.com";
3925
4097
  static helpText = 'API access is not enabled by default \u2014 you must request it from NameBright (contact support / your account manager) before the API Management page is available. Once enabled, create an API Application under my.namebright.com > My Account > API Management. The Client ID is "<accountName>:<applicationName>"; NameBright issues the Client Secret. Note the application also enforces an IP whitelist.';
@@ -3958,7 +4130,8 @@ var NameBrightRegistrar = class extends BaseRegistrar {
3958
4130
  client_id: this.credentials.clientId,
3959
4131
  client_secret: this.credentials.clientSecret
3960
4132
  },
3961
- ...opts
4133
+ // issuing a token changes nothing on the account, whatever it precedes
4134
+ ...asRead(opts)
3962
4135
  });
3963
4136
  if (!res.access_token) {
3964
4137
  throw new AuthenticationError("NameBright: token endpoint returned no access_token");
@@ -4737,7 +4910,11 @@ var NamecheapRegistrar = class extends BaseRegistrar {
4737
4910
  * has no WhoisGuard allotted (e.g. TLDs that don't offer it).
4738
4911
  */
4739
4912
  async setPrivacy(domainName, enabled, opts) {
4740
- const cr = await this.command("namecheap.domains.getInfo", { DomainName: domainName }, opts);
4913
+ const cr = await this.command(
4914
+ "namecheap.domains.getInfo",
4915
+ { DomainName: domainName },
4916
+ asRead(opts)
4917
+ );
4741
4918
  const id = cr.DomainGetInfoResult?.Whoisguard?.ID;
4742
4919
  if (id == null || String(id) === "0") {
4743
4920
  throw new Error(`${this.name}: ${domainName} has no WhoisGuard subscription to toggle`);
@@ -4977,7 +5154,7 @@ var NamecomHttpClient = class extends HttpClient {
4977
5154
  } catch (error) {
4978
5155
  if (error instanceof ParsingError) throw new ParsingError("namecom: invalid JSON response");
4979
5156
  if (error instanceof ConnectionError)
4980
- throw new ConnectionError("namecom: network request failed");
5157
+ throw new ConnectionError("namecom: network request failed", { notSent: error.notSent });
4981
5158
  throw error;
4982
5159
  }
4983
5160
  }
@@ -5002,6 +5179,8 @@ var NamecomHttpClient = class extends HttpClient {
5002
5179
  };
5003
5180
  var NamecomRegistrar = class extends BaseRegistrar {
5004
5181
  name = "namecom";
5182
+ // a REST API: GET and HEAD never change state
5183
+ static safeMethods = REST_SAFE_METHODS;
5005
5184
  static displayName = "Name.com";
5006
5185
  static website = "name.com";
5007
5186
  static supportsSandbox = true;
@@ -5030,7 +5209,11 @@ var NamecomRegistrar = class extends BaseRegistrar {
5030
5209
  headers: { Authorization: `Basic ${auth}` }
5031
5210
  };
5032
5211
  super(credentials, config, options);
5033
- this.http = new NamecomHttpClient({ ...config, options: this.options });
5212
+ this.http = new NamecomHttpClient({
5213
+ ...config,
5214
+ safeMethods: REST_SAFE_METHODS,
5215
+ options: this.options
5216
+ });
5034
5217
  }
5035
5218
  async testConnection(opts) {
5036
5219
  try {
@@ -5258,7 +5441,6 @@ var NamecomRegistrar = class extends BaseRegistrar {
5258
5441
  const old = index >= 0 ? remaining.splice(index, 1)[0] : void 0;
5259
5442
  await this.http.request({
5260
5443
  ...opts,
5261
- retries: 0,
5262
5444
  method: old ? "PUT" : "POST",
5263
5445
  path: old ? `${path}/${old.id}` : path,
5264
5446
  body: record
@@ -5267,7 +5449,6 @@ var NamecomRegistrar = class extends BaseRegistrar {
5267
5449
  for (const old of remaining) {
5268
5450
  await this.http.request({
5269
5451
  ...opts,
5270
- retries: 0,
5271
5452
  method: "DELETE",
5272
5453
  path: `${path}/${old.id}`
5273
5454
  });
@@ -5331,7 +5512,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
5331
5512
  }
5332
5513
  async mutate(req, opts) {
5333
5514
  try {
5334
- await this.http.request({ ...req, ...opts, retries: 0 });
5515
+ await this.http.request({ ...req, ...opts });
5335
5516
  return { success: true, message: "Domain updated successfully" };
5336
5517
  } catch (error) {
5337
5518
  return { success: false, message: toRegistrarError(error).message };
@@ -5744,7 +5925,7 @@ var NameSiloRegistrar = class extends BaseRegistrar {
5744
5925
  */
5745
5926
  async disableDnssec(domainName, opts) {
5746
5927
  try {
5747
- const res = await this.call("dnsSecListRecords", { domain: domainName }, opts);
5928
+ const res = await this.call("dnsSecListRecords", { domain: domainName }, asRead(opts));
5748
5929
  if (!replyOk(res)) return statusResult2(res);
5749
5930
  for (const d of ensureArray(res.reply?.ds_record)) {
5750
5931
  const del = await this.call(
@@ -5817,7 +5998,7 @@ var NameSiloRegistrar = class extends BaseRegistrar {
5817
5998
  desired.set(f.alias, list);
5818
5999
  }
5819
6000
  const currentAliases = new Set(
5820
- (await this.listEmailForwards(domainName, opts)).map((r) => r.alias).filter(Boolean)
6001
+ (await this.listEmailForwards(domainName, asRead(opts))).map((r) => r.alias).filter(Boolean)
5821
6002
  );
5822
6003
  for (const alias of currentAliases) {
5823
6004
  if (!desired.has(alias)) {
@@ -5908,7 +6089,7 @@ var NameSiloRegistrar = class extends BaseRegistrar {
5908
6089
  * delegation nameservers aren't part of this list (see `updateNameservers`).
5909
6090
  */
5910
6091
  async setDnsRecords(domainName, records, opts) {
5911
- const listRes = await this.call("dnsListRecords", { domain: domainName }, opts);
6092
+ const listRes = await this.call("dnsListRecords", { domain: domainName }, asRead(opts));
5912
6093
  if (!replyOk(listRes)) return { success: false, message: replyDetail(listRes) };
5913
6094
  const existing = ensureArray(listRes.reply?.resource_record);
5914
6095
  const existingByKey = /* @__PURE__ */ new Map();
@@ -6435,7 +6616,7 @@ var PorkbunRegistrar = class extends BaseRegistrar {
6435
6616
  * renewals aren't supported via the API.
6436
6617
  */
6437
6618
  async renewDomain(domainName, _years = 1, opts) {
6438
- const pricing = await this.tldPricing(extractTld(domainName), opts);
6619
+ const pricing = await this.tldPricing(extractTld(domainName), asRead(opts));
6439
6620
  if (pricing.renewal == null) {
6440
6621
  throw new Error(`${this.name}: could not determine the renewal price for ${domainName}`);
6441
6622
  }
@@ -6466,7 +6647,7 @@ var PorkbunRegistrar = class extends BaseRegistrar {
6466
6647
  * aren't supported via the API.
6467
6648
  */
6468
6649
  async transferIn(domainName, input, opts) {
6469
- const pricing = await this.tldPricing(extractTld(domainName), opts);
6650
+ const pricing = await this.tldPricing(extractTld(domainName), asRead(opts));
6470
6651
  if (pricing.transfer == null) {
6471
6652
  throw new Error(`${this.name}: could not determine the transfer price for ${domainName}`);
6472
6653
  }
@@ -6759,6 +6940,8 @@ function toDnsRecord3(r, domainName) {
6759
6940
  var WRITABLE_DNS_TYPES = /* @__PURE__ */ new Set(["A", "AAAA", "CNAME", "NS", "TXT", "MX"]);
6760
6941
  var SpaceshipRegistrar = class extends BaseRegistrar {
6761
6942
  name = "spaceship";
6943
+ // a REST API: GET and HEAD never change state
6944
+ static safeMethods = REST_SAFE_METHODS;
6762
6945
  static displayName = "Spaceship";
6763
6946
  static website = "spaceship.com";
6764
6947
  static helpText = 'Generate your API key and secret in the API Manager at Spaceship using the "New API key" button. The API requires both X-API-Key and X-API-Secret headers.';
@@ -7279,6 +7462,6 @@ function createRegistrar(name, credentials, options) {
7279
7462
  return new RegistrarClass(credentials, options);
7280
7463
  }
7281
7464
 
7282
- export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, ConfigurationError, ConnectionError, ConsentRequiredError, DEFAULT_OPTIONS, DynadotRegistrar, EXTENDED_FEATURES, Feature, GandiRegistrar, GoDaddyRegistrar, HttpClient, InvalidResponseError, MASKED_FORWARD_UNSUPPORTED, NameBrightRegistrar, NameSiloRegistrar, NamecheapRegistrar, NamecomRegistrar, NotFoundError, NotImplementedError, ParsingError, PorkbunRegistrar, RateLimitError, RegistrarClient, RegistrarError, RetryableRegistrarError, SpaceshipRegistrar, TimeoutError, USER_AGENT, backoffDelay, createDomain, createRegistrar, ensureArray, filterDomains, isCoreFeature, listPortfolio, normalizeDomain, normalizeNameservers, parseDate, parseXml, registrars, requireConsent, selectBaseUrl, settableForwards, sleep, toRegistrarError };
7465
+ export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, ConfigurationError, ConnectionError, ConsentRequiredError, DEFAULT_OPTIONS, DynadotRegistrar, EXTENDED_FEATURES, FEATURE_CALLS, Feature, GandiRegistrar, GoDaddyRegistrar, HttpClient, InvalidResponseError, MASKED_FORWARD_UNSUPPORTED, NameBrightRegistrar, NameSiloRegistrar, NamecheapRegistrar, NamecomRegistrar, NotFoundError, NotImplementedError, OutcomeUnknownError, ParsingError, PorkbunRegistrar, REST_SAFE_METHODS, RateLimitError, RegistrarClient, RegistrarError, RetryableRegistrarError, SpaceshipRegistrar, TimeoutError, USER_AGENT, asRead, backoffDelay, createDomain, createRegistrar, ensureArray, filterDomains, isCoreFeature, listPortfolio, markNotSent, normalizeDomain, normalizeNameservers, parseDate, parseXml, registrars, requireConsent, selectBaseUrl, settableForwards, sleep, toRegistrarError, wasNotSent };
7283
7466
  //# sourceMappingURL=index.js.map
7284
7467
  //# sourceMappingURL=index.js.map