| name | dns-providers |
| description | Implement DNS provider adapters for Cloudflare and AWS Route 53 in Node.js/TypeScript with a shared interface. |
| triggers | ["cloudflare api","route 53 sdk","dns provider adapter","cloudflare records","route53 records"] |
When to use this skill
Use this skill when implementing DNS provider adapters: Cloudflare API v4 via fetch, AWS Route 53 via the AWS SDK v3, or the shared DnsProvider interface.
Provider Interface
interface DnsRecord {
id: string;
zoneId: string;
name: string;
type: 'A' | 'AAAA' | 'CNAME' | 'MX' | 'TXT' | 'NS' | 'SRV' | 'CAA';
content: string;
ttl: number;
priority?: number;
proxied?: boolean;
comment?: string;
}
interface DnsZone {
id: string;
name: string;
provider: string;
status?: string;
}
interface DnsProvider {
name: string;
listZones(): Promise<DnsZone[]>;
listRecords(zoneId: string): Promise<DnsRecord[]>;
createRecord(zoneId: string, record: Omit<DnsRecord, 'id'>): Promise<DnsRecord>;
updateRecord(zoneId: string, recordId: string, record: Partial<DnsRecord>): Promise<DnsRecord>;
deleteRecord(zoneId: string, recordId: string): Promise<void>;
}
Cloudflare Adapter
import { z } from 'zod';
const CF_BASE = 'https://api.cloudflare.com/client/v4';
export class CloudflareProvider implements DnsProvider {
name = 'cloudflare';
constructor(private token: string, private allowedZoneIds?: string[]) {}
private async req<T>(path: string, options?: RequestInit): Promise<T> {
const res = await fetch(`${CF_BASE}${path}`, {
...options,
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json',
...options?.headers,
},
});
const json = (await res.json()) as { success: boolean; result: T; errors: { message: string }[] };
if (!json.) {
(json..( e.).());
}
json.;
}
(): <[]> {
zones = .<<{ : ; : ; : }>>();
zones
.( !. || ..(z.))
.( ({ : z., : z., : , : z. }));
}
(: ): <[]> {
records = .<<<, >>>();
records.( ({
: r. ,
zoneId,
: r. ,
: r. [],
: r. ,
: r. ,
: r. | ,
: r. | ,
: r. | ,
}));
}
(: , : <, >): <> {
result = .<<, >>(, {
: ,
: .(record),
});
{ ...record, : result. };
}
(: , : , : <>): <> {
result = .<<, >>(
,
{ : , : .(record) },
);
result ;
}
(: , : ): <> {
.(, { : });
}
}
Cloudflare TTL Values
| Value | Meaning |
|---|
1 | Auto (proxied records only) |
60 | 1 minute |
300 | 5 minutes |
3600 | 1 hour |
86400 | 1 day |
Route 53 Adapter
import {
Route53Client,
ListHostedZonesCommand,
ListResourceRecordSetsCommand,
ChangeResourceRecordSetsCommand,
ChangeAction,
} from '@aws-sdk/client-route-53';
export class Route53Provider implements DnsProvider {
name = 'route53';
private client: Route53Client;
constructor(region: string, private allowedZoneIds?: string[]) {
this.client = new Route53Client({ region });
}
async listZones(): Promise<DnsZone[]> {
const { HostedZones = [] } = await this.client.send(new ListHostedZonesCommand({}));
return HostedZones.filter((z) => {
const id = z.Id?.replace('/hostedzone/', '') ?? '';
!. || ..(id);
}).( ({
: z.?.(, ) ?? ,
: z.?.(, ) ?? ,
: ,
}));
}
(: ): <[]> {
{ = [] } = ..(
({ : zoneId }),
);
.(
(rrs. ?? []).( ({
: ,
zoneId,
: rrs.?.(, ) ?? ,
: rrs. [],
: rr. ?? ,
: rrs. ?? ,
: rrs. === ? (rr.?.()[] ?? , ) : ,
})),
);
}
(: , : <, >): <> {
..( ({
: zoneId,
: {
: [{
: .,
: {
: record.,
: record.,
: record.,
: [{ : record. }],
},
}],
},
}));
{ ...record, : };
}
(: , : ): <> {
records = .(zoneId);
rec = records.( r. === recordId);
(!rec) ();
..( ({
: zoneId,
: {
: [{
: .,
: {
: rec.,
: rec.,
: rec.,
: [{ : rec. }],
},
}],
},
}));
}
(: , : , : <>): <> {
records = .(zoneId);
existing = records.( r. === recordId);
(!existing) ();
.(zoneId, recordId);
updated = { ...existing, ...updates };
.(zoneId, updated);
}
}
Provider Factory
export function createProviders(env: NodeJS.ProcessEnv): DnsProvider[] {
const providers: DnsProvider[] = [];
if (env.CLOUDFLARE_API_TOKEN) {
const zoneIds = env.CLOUDFLARE_ZONE_IDS?.split(',').map((s) => s.trim()).filter(Boolean);
providers.push(new CloudflareProvider(env.CLOUDFLARE_API_TOKEN, zoneIds));
}
if (env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY) {
const zoneIds = env.ROUTE53_HOSTED_ZONE_IDS?.split(',').map((s) => s.trim()).filter(Boolean);
providers.push(new Route53Provider(env.AWS_REGION ?? 'us-east-1', zoneIds));
}
if (env.STATIC_ZONES_FILE) {
providers.push(new StaticProvider(env.));
}
providers;
}
Zone ID Encoding
Cloudflare zone IDs are alphanumeric strings (e.g. abc123def456).
Route 53 hosted zone IDs are returned as /hostedzone/Z1234567890AB -- strip the prefix when storing or passing as a parameter.
Error Handling
| Provider | Error Class | Message Pattern |
|---|
| Cloudflare | Throws from req() | Cloudflare API error codes (9xxx) |
| Route 53 | AWS SDK throws ServiceException | HTTP status + message |
| Both | Wrap in provider-agnostic Error before returning to API layer | |