@aoxborrow/registrar-client 0.1.0 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aoxborrow/registrar-client",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "One consistent, fully-typed TypeScript interface to many domain registrar APIs (GoDaddy, Cloudflare, Namecheap, Spaceship, Dynadot, Porkbun, and more) behind a single pluggable multi-provider client",
5
5
  "author": {
6
6
  "name": "Aaron Oxborrow",
@@ -11,6 +11,7 @@
11
11
  "access": "public"
12
12
  },
13
13
  "homepage": "https://github.com/aoxborrow/registrar-client",
14
+ "funding": "https://github.com/sponsors/aoxborrow",
14
15
  "repository": {
15
16
  "type": "git",
16
17
  "url": "git+https://github.com/aoxborrow/registrar-client.git"
package/src/client.ts CHANGED
@@ -50,8 +50,10 @@ export class RegistrarClient {
50
50
  const detail = await this.provider.getDomain(name, opts);
51
51
  let merged = { ...domains[index], ...detail };
52
52
  // Some providers expose nameservers only via a dedicated endpoint, not
53
- // on the domain detail — fetch them separately when still empty.
54
- if (merged.nameservers.length === 0) {
53
+ // on the domain detail — fetch them separately when still empty. Skip
54
+ // providers whose getNameservers just re-reads getDomain: it would
55
+ // repeat the same call and return the same empty result.
56
+ if (merged.nameservers.length === 0 && this.provider.requiresNameserversFetch) {
55
57
  try {
56
58
  const nameservers = normalizeNameservers(
57
59
  await this.provider.getNameservers(name, opts)
package/src/registrar.ts CHANGED
@@ -75,6 +75,12 @@ export abstract class BaseRegistrar implements Registrar {
75
75
  return this.features.includes(feature);
76
76
  }
77
77
 
78
+ // See Registrar.requiresNameserversFetch. Defaults true, so a provider
79
+ // whose getNameservers hits a real endpoint keeps the enrichment fallback
80
+ // without opting in; the providers whose getNameservers just re-reads getDomain
81
+ // override this to false to drop the redundant call.
82
+ readonly requiresNameserversFetch: boolean = true;
83
+
78
84
  constructor(
79
85
  credentials: RegistrarCredentials,
80
86
  httpConfig: Omit<HttpClientConfig, 'options'>,
@@ -175,6 +175,7 @@ export class CloudflareRegistrar extends BaseRegistrar {
175
175
  readonly name = 'cloudflare';
176
176
 
177
177
  static readonly displayName = 'Cloudflare';
178
+ static readonly website = 'cloudflare.com';
178
179
  static readonly helpText =
179
180
  'Create an API token in your Cloudflare dashboard under My Profile > API Tokens. ' +
180
181
  'The token needs "Account.Registrar" read/write permissions. Find your Account ID ' +
@@ -180,6 +180,7 @@ export class DynadotRegistrar extends BaseRegistrar {
180
180
  readonly name = 'dynadot';
181
181
 
182
182
  static readonly displayName = 'Dynadot';
183
+ static readonly website = 'dynadot.com';
183
184
  static readonly helpText =
184
185
  'Under Tools > API in your Dynadot account, generate an API Key and API Secret. ' +
185
186
  'The account must be unlocked with API access enabled. The key identifies you; ' +
@@ -320,6 +321,10 @@ export class DynadotRegistrar extends BaseRegistrar {
320
321
  return data.domain_info;
321
322
  }
322
323
 
324
+ // getNameservers re-reads getDomain (domain_info carries NS inline; no separate
325
+ // endpoint), so listing enrichment gains nothing from the fallback — skip it.
326
+ override readonly requiresNameserversFetch = false;
327
+
323
328
  override async getNameservers(domainName: string, opts?: RequestOptions): Promise<string[]> {
324
329
  // domain_info carries the nameservers inline (empty for Dynadot-DNS-hosted
325
330
  // domains, which have no external NS)
@@ -161,6 +161,7 @@ export class GandiRegistrar extends BaseRegistrar {
161
161
  readonly name = 'gandi';
162
162
 
163
163
  static readonly displayName = 'Gandi.net';
164
+ static readonly website = 'gandi.net';
164
165
  static readonly helpText =
165
166
  'Generate a Personal Access Token (PAT) in your Gandi account under Account ' +
166
167
  'Settings > Security. The token needs permission to manage domains. (Gandi has ' +
@@ -269,6 +269,7 @@ export class GoDaddyRegistrar extends BaseRegistrar {
269
269
  readonly name = 'godaddy';
270
270
 
271
271
  static readonly displayName = 'GoDaddy';
272
+ static readonly website = 'godaddy.com';
272
273
  static readonly helpText =
273
274
  'Authenticate one of two ways. For production, create a Personal Access ' +
274
275
  'Token (PAT) at https://developer.godaddy.com and pass it as `apiToken` — ' +
@@ -420,6 +421,10 @@ export class GoDaddyRegistrar extends BaseRegistrar {
420
421
  return this.toDomain(d);
421
422
  }
422
423
 
424
+ // getNameservers re-reads getDomain (GoDaddy has no dedicated NS endpoint), so
425
+ // listing enrichment gains nothing from the fallback — skip it.
426
+ override readonly requiresNameserversFetch = false;
427
+
423
428
  override async getNameservers(domainName: string, opts?: RequestOptions): Promise<string[]> {
424
429
  const domain = await this.getDomain(domainName, opts);
425
430
  return domain.nameservers;
@@ -198,6 +198,7 @@ export class NameBrightRegistrar extends BaseRegistrar {
198
198
  readonly name = 'namebright';
199
199
 
200
200
  static readonly displayName = 'NameBright';
201
+ static readonly website = 'namebright.com';
201
202
  static readonly helpText =
202
203
  'API access is not enabled by default — you must request it from NameBright ' +
203
204
  '(contact support / your account manager) before the API Management page is ' +
@@ -188,6 +188,7 @@ export class NamecheapRegistrar extends BaseRegistrar {
188
188
  readonly name = 'namecheap';
189
189
 
190
190
  static readonly displayName = 'Namecheap';
191
+ static readonly website = 'namecheap.com';
191
192
  static readonly helpText =
192
193
  'Enable API access in your Namecheap account under Profile > Tools > API Access, ' +
193
194
  'generate an API key, and whitelist your request IP(s) in the API settings. ' +
@@ -157,6 +157,7 @@ export class NameSiloRegistrar extends BaseRegistrar {
157
157
  readonly name = 'namesilo';
158
158
 
159
159
  static readonly displayName = 'NameSilo';
160
+ static readonly website = 'namesilo.com';
160
161
  static readonly helpText =
161
162
  'Generate an API key in your NameSilo account under Account Options > API Manager. ' +
162
163
  'You can optionally restrict the key to specific IP addresses there. For sandbox ' +
@@ -285,6 +286,10 @@ export class NameSiloRegistrar extends BaseRegistrar {
285
286
  });
286
287
  }
287
288
 
289
+ // getNameservers re-reads getDomain (getDomainInfo carries NS; no separate
290
+ // endpoint), so listing enrichment gains nothing from the fallback — skip it.
291
+ override readonly requiresNameserversFetch = false;
292
+
288
293
  override async getNameservers(domainName: string, opts?: RequestOptions): Promise<string[]> {
289
294
  const domain = await this.getDomain(domainName, opts);
290
295
  return domain.nameservers;
@@ -182,6 +182,7 @@ export class PorkbunRegistrar extends BaseRegistrar {
182
182
  readonly name = 'porkbun';
183
183
 
184
184
  static readonly displayName = 'Porkbun';
185
+ static readonly website = 'porkbun.com';
185
186
  static readonly helpText =
186
187
  'Create an API key and secret in your Porkbun account under Account > API Access, ' +
187
188
  'then enable "API Access" on each domain you want to manage (per-domain toggle in ' +
@@ -133,6 +133,7 @@ export class SpaceshipRegistrar extends BaseRegistrar {
133
133
  readonly name = 'spaceship';
134
134
 
135
135
  static readonly displayName = 'Spaceship';
136
+ static readonly website = 'spaceship.com';
136
137
  static readonly helpText =
137
138
  'Generate your API key and secret in the API Manager at Spaceship using the ' +
138
139
  '"New API key" button. The API requires both X-API-Key and X-API-Secret headers.';
@@ -203,6 +204,10 @@ export class SpaceshipRegistrar extends BaseRegistrar {
203
204
  return this.toDomain(await this.getRaw(domainName, opts));
204
205
  }
205
206
 
207
+ // getNameservers reads the same getRaw payload as getDomain (no dedicated NS
208
+ // endpoint), so listing enrichment gains nothing from the fallback — skip it.
209
+ override readonly requiresNameserversFetch = false;
210
+
206
211
  override async getNameservers(domainName: string, opts?: RequestOptions): Promise<string[]> {
207
212
  const d = await this.getRaw(domainName, opts);
208
213
  return d.nameservers?.hosts ?? [];
package/src/types.ts CHANGED
@@ -307,6 +307,13 @@ export interface Registrar {
307
307
  // the capabilities this provider supports (core contract + its extended features)
308
308
  readonly features: readonly RegistrarFeature[];
309
309
 
310
+ // Whether reading a domain's nameservers needs a getNameservers call beyond
311
+ // getDomain. True when getNameservers hits its own endpoint that can surface
312
+ // nameservers getDomain omits; false when it just re-reads getDomain, so
313
+ // listing enrichment skips the redundant fetch. See BaseRegistrar for the
314
+ // default.
315
+ readonly requiresNameserversFetch: boolean;
316
+
310
317
  // whether this provider supports a given capability
311
318
  supports(feature: RegistrarFeature): boolean;
312
319
 
@@ -431,11 +438,13 @@ export interface Registrar {
431
438
  }
432
439
 
433
440
  // static side of a registrar class: the constructor plus discovery metadata
434
- // (display name, the credential fields it needs, where to get them, and whether
435
- // it offers a sandbox/test environment).
441
+ // (display name, public website, the credential fields it needs, where to get
442
+ // them, and whether it offers a sandbox/test environment).
436
443
  export interface RegistrarConstructor {
437
444
  new (credentials: RegistrarCredentials, options?: RegistrarOptions): Registrar;
438
445
  readonly displayName: string;
446
+ // the provider's public website domain, e.g. "gandi.net"
447
+ readonly website: string;
439
448
  readonly configFields: ConfigField[];
440
449
  readonly helpText: string;
441
450
  readonly supportsSandbox: boolean;