@hasna/domains
Version:
Domain portfolio, registrar, marketplace, and DNS management for AI agents — CLI + MCP + HTTP API + SDK, shared account API and PostgreSQL
345 lines • 13.6 kB
TypeScript
import type { DnsRecordGroupState } from "./cloudflare.js";
export declare const PROVISIONING_STATUSES: readonly ["requested", "quoted", "registration_submitting", "registration_submitted", "registered", "zone_ready", "nameservers_submitted", "delegated", "worker_bound", "ready", "manual_review", "failed"];
export type ProvisioningStatus = (typeof PROVISIONING_STATUSES)[number];
export type OriginTlsMode = "strict" | "full";
export type ProvisioningRegistrar = "route53" | "brandsight";
export interface DomainProvisioningRequest {
name: string;
idempotency_key: string;
max_price_usd: number;
years: number;
auto_renew: boolean;
acquisition_mode: "purchase" | "adopt";
registrar: ProvisioningRegistrar;
dns_provider: "cloudflare";
target: "shortlinks" | "website_origin";
worker_name: string | null;
origin_hostname: string | null;
origin_tls_mode: OriginTlsMode | null;
}
export interface ProvisionedWebRecord {
type: "CNAME";
name: string;
value: string;
proxied: true;
ttl: number;
}
export interface DomainProvisioningResult {
zone_ref: string;
nameservers: string[];
web_records: ProvisionedWebRecord[];
origin_tls_mode: OriginTlsMode | null;
checked_at: string;
}
export declare const HOSTED_DNS_RECORD_TYPES: readonly ["TXT", "CNAME", "MX"];
export type HostedDnsRecordType = (typeof HOSTED_DNS_RECORD_TYPES)[number];
export interface HostedDnsRecord {
type: HostedDnsRecordType;
name: string;
value: string;
ttl: number;
priority: number | null;
}
export interface EffectiveHostedDnsRecord extends HostedDnsRecord {
/** Server-derived TXT presentation decoding; raw value remains authoritative for preservation. */
semantic_value?: string;
}
export type AppliedHostedDnsRecords = HostedDnsRecord[] | {
records: HostedDnsRecord[];
verifiedGroup: DnsRecordGroupState;
};
export interface DomainDnsReconciliation {
id: string;
provisioning_job_id: string;
domain_name: string;
idempotency_key: string;
request_hash: string;
status: "requested" | "applying" | "ready" | "manual_review";
records: HostedDnsRecord[];
result: {
records: EffectiveHostedDnsRecord[];
checked_at: string;
} | null;
error: string | null;
lease_token: string | null;
lease_until: string | null;
created_at: string;
updated_at: string;
}
export type PublicDomainDnsReconciliation = Omit<DomainDnsReconciliation, "lease_token">;
export declare function publicDnsReconciliation(reconciliation: DomainDnsReconciliation): PublicDomainDnsReconciliation;
export interface DomainProvisioningProviderState {
route53_account_ref?: string;
route53_account_binding_sha256?: string;
quoted_price_usd?: number;
currency?: string;
registration_operation_id?: string;
nameserver_operation_id?: string;
cloudflare_zone_id?: string;
cloudflare_nameservers?: string[];
route53_zone_ids_before_registration?: string[];
route53_hosted_zone_id?: string;
route53_hosted_zone_cleaned?: boolean;
worker_domain_bound?: boolean;
website_origin_configured?: boolean;
origin_tls_mode_configured?: OriginTlsMode | "origin_pull";
origin_tls_mode_checked_at?: string;
web_records?: ProvisionedWebRecord[];
target_checked_at?: string;
last_provider_status?: string;
registration_submitted_at?: string;
nameservers_submitted_at?: string;
delegation_dns_preserved_count?: number;
delegation_dns_preserved_sha256?: string;
delegation_dns_preserved_at?: string;
}
export interface PortfolioDomainRegistration extends RegisteredDomainDetail {
id: string;
status: string;
registrar: string;
}
export interface DomainProvisioningJob extends DomainProvisioningRequest {
id: string;
domain_id: string;
request_hash: string;
status: ProvisioningStatus;
provider_state: DomainProvisioningProviderState;
attempts: number;
error: string | null;
lease_token: string | null;
lease_until: string | null;
created_at: string;
updated_at: string;
}
export interface DomainProvisioningStore {
reserve(request: DomainProvisioningRequest, requestHash: string): Promise<DomainProvisioningJob>;
get(id: string): Promise<DomainProvisioningJob | null>;
getByName(name: string): Promise<DomainProvisioningJob | null>;
getPortfolioRegistration(name: string): Promise<PortfolioDomainRegistration | null>;
listRunnable(limit: number): Promise<DomainProvisioningJob[]>;
claim(id: string, leaseToken: string, leaseUntil: string): Promise<DomainProvisioningJob | null>;
update(id: string, patch: Partial<Pick<DomainProvisioningJob, "status" | "provider_state" | "attempts" | "error" | "lease_token" | "lease_until">>, leaseToken?: string): Promise<DomainProvisioningJob | null>;
markPortfolioReady(job: DomainProvisioningJob, detail: RegisteredDomainDetail): Promise<void>;
reserveAdoption(request: DomainProvisioningRequest, requestHash: string, detail: RegisteredDomainDetail, providerState?: DomainProvisioningProviderState): Promise<DomainProvisioningJob>;
reserveDnsReconciliation(input: {
job: DomainProvisioningJob;
idempotencyKey: string;
requestHash: string;
records: HostedDnsRecord[];
}): Promise<DomainDnsReconciliation>;
listDnsReconciliations(name: string, query: {
id?: string;
idempotency_key?: string;
limit: number;
offset: number;
}): Promise<DomainDnsReconciliation[]>;
withDnsZoneLock<T>(zoneId: string, work: (store: DomainProvisioningStore) => Promise<T>): Promise<T>;
claimDnsReconciliation(id: string, leaseToken: string, leaseUntil: string): Promise<DomainDnsReconciliation | null>;
updateDnsReconciliation(id: string, patch: Partial<Pick<DomainDnsReconciliation, "status" | "result" | "error" | "lease_token" | "lease_until">>, leaseToken: string): Promise<DomainDnsReconciliation | null>;
}
export interface AvailabilityQuote {
available: boolean;
price_usd?: number;
registration_price_usd?: number;
renewal_price_usd?: number;
currency?: string;
is_premium?: boolean;
}
export interface ProviderOperationStatus {
status: string;
message?: string;
}
export interface Route53AccountBinding {
route53_account_ref: string;
route53_account_binding_sha256: string;
}
export interface RegisteredDomainDetail {
registered_at?: string;
expires_at?: string;
auto_renew?: boolean;
nameservers: string[];
registrar?: string;
}
export interface DelegationDnsPreservation {
count: number;
sha256: string;
}
export interface CloudflareZoneState {
id: string;
status: string;
nameservers: string[];
}
export interface DomainProvisioningProviders {
checkAvailability(name: string): Promise<AvailabilityQuote>;
submitRegistration(input: {
name: string;
years: number;
autoRenew: boolean;
}): Promise<{
operationId: string;
}>;
getOperationStatus(operationId: string, route53Binding?: Route53AccountBinding): Promise<ProviderOperationStatus>;
getDomainDetail(name: string, registrar?: ProvisioningRegistrar, route53Binding?: Route53AccountBinding): Promise<RegisteredDomainDetail | null>;
resolveAdoptionAccount?(name: string): Promise<(Route53AccountBinding & {
detail: RegisteredDomainDetail;
}) | null>;
ensureCloudflareZone(name: string): Promise<CloudflareZoneState>;
updateNameservers(name: string, nameservers: string[], registrar?: ProvisioningRegistrar, route53Binding?: Route53AccountBinding): Promise<{
operationId: string;
completed?: boolean;
}>;
resolvePublicNameservers(name: string): Promise<string[]>;
preserveRegistrarDnsBeforeDelegation(input: {
hostname: string;
zoneId: string;
registrar: ProvisioningRegistrar;
route53Binding?: Route53AccountBinding;
}): Promise<DelegationDnsPreservation>;
bindWorkerDomain(input: {
hostname: string;
zoneId: string;
workerName: string;
}): Promise<void>;
workerDomainReady(input: {
hostname: string;
zoneId: string;
workerName: string;
}): Promise<boolean>;
configureWebsiteOrigin(input: {
hostname: string;
zoneId: string;
originHostname: string;
}): Promise<ProvisionedWebRecord[]>;
ensureWebsiteOriginTls(input: {
zoneId: string;
requestedMode: OriginTlsMode;
}): Promise<{
mode: OriginTlsMode | "origin_pull";
changed: boolean;
downgradeRefused: boolean;
}>;
websiteOriginReady(input: {
hostname: string;
zoneId: string;
originHostname: string;
originTlsMode: OriginTlsMode;
}): Promise<boolean>;
getDnsRecordState?(input: {
zoneId: string;
recordName: string;
recordType: HostedDnsRecordType;
}): Promise<DnsRecordGroupState>;
reconcileDnsRecords(input: {
hostname: string;
zoneId: string;
records: HostedDnsRecord[];
mode?: "preserve_mail";
assertLease?: () => Promise<void>;
deadline?: number;
expectedCurrentRecordsSha256?: string;
}): Promise<AppliedHostedDnsRecords>;
dnsRecordsReady(input: {
assertLease?: () => Promise<void>;
deadline?: number;
hostname: string;
zoneId: string;
records: HostedDnsRecord[];
mode?: "preserve_mail";
verifiedGroup?: DnsRecordGroupState;
}): Promise<boolean>;
listRoute53HostedZoneIds?(name: string): Promise<string[]>;
cleanupRoute53HostedZone?(input: {
name: string;
hostedZoneId: string;
registrarNameservers: string[];
}): Promise<boolean>;
}
export declare class RegistrarActionRequiredError extends Error {
constructor(message: string);
}
export interface ProvisioningWorkerOptions {
intervalMs?: number;
batchSize?: number;
leaseMs?: number;
maxAttempts?: number;
shortlinksWorkerName?: string;
now?: () => Date;
log?: (event: string, detail: Record<string, unknown>) => void;
}
/** Current canonical hash for a normalized provisioning request. */
export declare function provisioningRequestHash(request: DomainProvisioningRequest): string;
/**
* Accept a stored request hash only when it is either the current canonical
* hash or the exact pre-origin-fields hash used by legacy Shortlinks purchase
* jobs. The caller-supplied current hash must itself be canonical, so an
* arbitrary mismatched hash cannot use this compatibility path.
*/
export declare function provisioningRequestHashMatches(storedHash: string, currentHash: string, request: DomainProvisioningRequest): boolean;
export declare function normalizeHostedDnsRecords(value: unknown, domainValue: string, allowRetainedAutomaticTtl?: boolean): HostedDnsRecord[];
/** A hosted website origin is an AWS ALB DNS hostname, never a URL or an IP. */
export declare function normalizeWebsiteOriginHostname(value: unknown): string;
export declare class DomainProvisioningService {
private readonly store;
private readonly providers;
private readonly intervalMs;
private readonly batchSize;
private readonly leaseMs;
private readonly maxAttempts;
private readonly shortlinksWorkerName;
private readonly now;
private readonly log;
private timer;
private scheduledRunActive;
constructor(store: DomainProvisioningStore, providers: DomainProvisioningProviders, options?: ProvisioningWorkerOptions);
quote(name: string): Promise<{
name: string;
} & AvailabilityQuote>;
request(input: Partial<DomainProvisioningRequest>): Promise<DomainProvisioningJob>;
adopt(input: {
name?: unknown;
idempotency_key?: unknown;
dns_provider?: unknown;
target?: unknown;
worker_name?: unknown;
origin_hostname?: unknown;
origin_tls_mode?: unknown;
}): Promise<DomainProvisioningJob>;
get(id: string): Promise<DomainProvisioningJob | null>;
getByName(name: string): Promise<DomainProvisioningJob | null>;
listDnsReconciliations(name: string, query?: {
id?: unknown;
idempotency_key?: unknown;
limit?: unknown;
offset?: unknown;
}): Promise<{
reconciliations: DomainDnsReconciliation[];
next_offset: number | null;
}>;
getDnsState(name: string, recordName: unknown, recordType: unknown): Promise<DnsRecordGroupState & {
domain_name: string;
provisioning_job_id: string;
}>;
reconcileDns(name: string, input: {
idempotency_key?: unknown;
records?: unknown;
mode?: unknown;
expected_current_records_sha256?: unknown;
}): Promise<DomainDnsReconciliation>;
advance(id: string): Promise<DomainProvisioningJob>;
private advanceClaimed;
runOnce(): Promise<{
processed: number;
ready: number;
failed: number;
}>;
private runScheduledOnce;
start(): void;
stop(): void;
}
export declare function isProvisioningTerminal(status: ProvisioningStatus): boolean;
export type PublicDomainProvisioningJob = Omit<DomainProvisioningJob, "lease_token"> & {
result: DomainProvisioningResult | null;
};
/** Strip the worker lease credential from every API response. */
export declare function publicProvisioningJob(job: DomainProvisioningJob): PublicDomainProvisioningJob;
//# sourceMappingURL=provisioning.d.ts.map