@hasna/domains
Version:
Domain portfolio, registrar, marketplace, and DNS management for AI agents — CLI + MCP + HTTP API + SDK, shared account API and PostgreSQL
217 lines • 10.4 kB
TypeScript
/**
* Cloudflare DNS provider — zone and record management via Cloudflare API v4
*
* Env vars:
* CLOUDFLARE_API_TOKEN — API token with Zone:Edit + DNS:Edit permissions
* CLOUDFLARE_ACCOUNT_ID — Account ID (required for zone creation)
*/
import type { DnsProvider, DomainInventoryProvider } from "./registrar.js";
import { type CloudflareConfig } from "./cloudflare-auth.js";
export type { CloudflareConfig };
export declare class CloudflareApiError extends Error {
statusCode: number;
providerCode?: string | undefined;
constructor(message: string, statusCode: number, providerCode?: string | undefined);
}
export interface CloudflareZone {
id: string;
name: string;
status: string;
nameservers: string[];
original_nameservers?: string[];
}
export interface CloudflareRecord {
id?: string;
type: string;
name: string;
content: string;
ttl: number;
priority?: number;
proxied?: boolean;
}
/** One new DNS-only CNAME. Both the zone identity and full record name are explicit. */
export interface CloudflareCreateRecordInput {
zoneId: string;
zoneName: string;
type: "CNAME";
name: string;
content: string;
ttl: number;
proxied: false;
}
export interface CloudflareCreateRecordReceipt {
created: true;
provider: "cloudflare";
zone: {
id: string;
name: string;
};
record: CloudflareRecord & {
id: string;
};
}
export type CloudflareOriginTlsMode = "strict" | "full";
export type CloudflareObservedOriginTlsMode = "off" | "flexible" | "full" | "strict" | "origin_pull";
export declare function getConfig(): CloudflareConfig;
export declare function boundedResponseText(response: Response, maxBytes: number, signal: AbortSignal): Promise<string>;
/**
* Create exactly one absent CNAME. Never updates, deletes or retries a write.
* Cloudflare's CNAME collision check protects against competing address/CNAME
* creation after preflight; this is not a general record compare-and-swap API.
* A failed or ambiguous POST must be reconciled with a provider read before a
* caller chooses another operation. Even an identical existing record refuses.
*/
export declare function createRecordIfAbsent(input: CloudflareCreateRecordInput, config?: CloudflareConfig): Promise<CloudflareCreateRecordReceipt>;
/** One existing live record changed in place. Zone and record IDs and the zone name are explicit. */
export interface CloudflareUpdateRecordInput {
zoneId: string;
zoneName: string;
recordId: string;
/**
* Fields to change (at least one of content, ttl, proxied). `expectContent` is a
* read-only precondition: when given, a record whose current content differs is
* refused before any write.
*/
update: {
content?: string;
ttl?: number;
proxied?: boolean;
expectContent?: string;
};
}
export interface CloudflareRecordUpdateReceipt {
changed: true;
provider: "cloudflare";
zone: {
id: string;
name: string;
};
/** The exact pre-change record; a rollback applies the same fields back to this state. */
previous: CloudflareRecord & {
id: string;
};
record: CloudflareRecord & {
id: string;
};
}
/**
* Change one existing record IN PLACE by record ID: one PATCH of only the fields
* given, then a readback that must match. The record ID, type and name are
* preserved; nothing is deleted, recreated or retried. Cloudflare validates the
* record's own content semantics. A failed or ambiguous call must be reconciled
* with a provider read before another attempt.
*/
export declare function updateRecordInPlace(input: CloudflareUpdateRecordInput, config?: CloudflareConfig): Promise<CloudflareRecordUpdateReceipt>;
export declare function listZones(config?: CloudflareConfig): Promise<CloudflareZone[]>;
export declare function getZone(domain: string, config?: CloudflareConfig): Promise<CloudflareZone | null>;
export declare function createZone(domain: string, config?: CloudflareConfig): Promise<CloudflareZone>;
/**
* Find-or-create a Cloudflare zone and return its assigned nameservers.
* Idempotent: reuses an existing zone for the domain if present. The returned
* nameservers are what the registrar should be delegated to when Cloudflare is
* the selected DNS provider.
*
* `deps` is injectable for testing; defaults to the real getZone/createZone.
*/
export declare function ensureZone(domain: string, config?: CloudflareConfig, deps?: {
getZone: (d: string, c?: CloudflareConfig) => Promise<CloudflareZone | null>;
createZone: (d: string, c?: CloudflareConfig) => Promise<CloudflareZone>;
}): Promise<CloudflareZone>;
export declare function deleteZone(zoneId: string, config?: CloudflareConfig): Promise<void>;
/** Read the zone's current edge-to-origin TLS mode. */
export declare function getZoneOriginTlsMode(zoneId: string, config?: CloudflareConfig): Promise<CloudflareObservedOriginTlsMode>;
/**
* Apply one explicit zone TLS intent and read it back. A zone already using
* strict or origin-pull mode is never silently weakened. Automatic SSL/TLS is
* a separate Cloudflare setting and is deliberately not changed here.
*/
export declare function ensureZoneOriginTlsMode(zoneId: string, requested: CloudflareOriginTlsMode, config?: CloudflareConfig): Promise<{
mode: CloudflareOriginTlsMode | "origin_pull";
changed: boolean;
downgradeRefused: boolean;
}>;
/**
* Zone settings this package may read or change, with their accepted values.
* Deliberately an allowlist: a setting is added here with its values before any
* command can reach it, and nothing else on the zone is ever written.
*/
export declare const CLOUDFLARE_ZONE_SETTINGS: {
/** Scrape Shield email-address obfuscation, as Cloudflare reports it. */
readonly email_obfuscation: readonly ["on", "off"];
};
export type CloudflareZoneSettingName = keyof typeof CLOUDFLARE_ZONE_SETTINGS;
/** Read one allowlisted zone setting exactly as the provider reports it. */
export declare function getZoneSetting(zoneId: string, setting: string, config?: CloudflareConfig): Promise<string>;
/**
* Set one allowlisted zone setting to an explicit value and read it back.
* A zone already at the requested value costs one read and no write. A change
* is a single PATCH of that setting only; no other setting is touched, and a
* readback that does not match the requested value fails the operation.
*/
export declare function ensureZoneSetting(zoneId: string, setting: string, value: string, config?: CloudflareConfig): Promise<{
setting: CloudflareZoneSettingName;
value: string;
changed: boolean;
}>;
/** Retry only contradictory/incomplete enumeration evidence, never a provider write. */
export declare class CloudflareDnsPaginationError extends Error {
constructor(reason: string, page: number, count: number, metadata?: Record<string, unknown>);
}
export declare const DNS_MAIL_RECONCILIATION_TIMEOUT_MS = 25000;
export interface DnsReadOptions {
assertLease?: () => Promise<void>;
deadline?: number;
}
/** A fresh complete enumeration per attempt; no records survive a rejected attempt. */
export declare function listRecords(zoneId: string, config?: CloudflareConfig, options?: DnsReadOptions): Promise<CloudflareRecord[]>;
export declare function upsertRecord(zoneId: string, record: CloudflareRecord, config?: CloudflareConfig): Promise<void>;
/** Reconcile only the explicitly named DNS record groups, leaving every other group untouched. */
export declare function reconcileRecords(zoneId: string, records: CloudflareRecord[], config?: CloudflareConfig): Promise<void>;
export interface DnsRecordGroupState {
hash_scheme: "domains-dns-group-v1";
zone_id: string;
record_name: string;
record_type: string;
records: Array<{
id: string;
type: string;
name: string;
value: string;
ttl: number;
priority: number | null;
proxied: boolean | null;
}>;
records_sha256: string;
}
/** Public hash contract: SHA256 of UTF-8 JSON of the returned fields except records_sha256.
* Object keys use the order below; records sort by provider id (JS lexical order).
* The complete provider population is read before selecting a group; never truncate.
*/
export declare function getDnsRecordGroupState(zoneId: string, type: string, name: string, config?: CloudflareConfig, options?: DnsReadOptions): Promise<DnsRecordGroupState>;
/** Guarded single-group reconciliation retains exact matching records and their ids.
* The caller holds the service zone lock. Cloudflare offers no atomic CAS against
* external writers; compare immediately before writes and verify the complete result.
*/
export declare function reconcileGuardedRecords(zoneId: string, desired: CloudflareRecord[], expectedHash: string, config?: CloudflareConfig, assertLease?: () => Promise<void>): Promise<DnsRecordGroupState>;
/** Ensure mail authorization without replacing existing record groups. */
export declare function reconcileMailRecords(zoneId: string, records: CloudflareRecord[], config?: CloudflareConfig, assertLease?: () => Promise<void>, operationDeadline?: number): Promise<CloudflareRecord[]>;
export declare function deleteRecord(zoneId: string, recordId: string, config?: CloudflareConfig): Promise<void>;
export declare function deleteRecordByNameType(zoneId: string, name: string, type: string, config?: CloudflareConfig): Promise<void>;
export declare function createCloudflareProvider(config?: CloudflareConfig): DnsProvider & DomainInventoryProvider;
export interface CloudflareWorkerDomain {
id?: string;
zone_id?: string;
zone_name?: string;
hostname: string;
service: string;
environment?: string;
enabled?: boolean;
}
export declare function listWorkerCustomDomains(config?: CloudflareConfig): Promise<CloudflareWorkerDomain[]>;
export declare function bindWorkerCustomDomain(hostname: string, zoneId: string, workerName?: string, config?: CloudflareConfig): Promise<CloudflareWorkerDomain>;
export declare function workerCustomDomainReady(hostname: string, zoneId: string, workerName?: string, config?: CloudflareConfig, fetchImpl?: typeof fetch, readiness?: {
path?: string;
header?: string;
value?: string;
}): Promise<boolean>;
//# sourceMappingURL=cloudflare.d.ts.map