import type { Context } from "../context.ts"; import { Resource } from "../resource.ts"; import { memoize } from "../util/memoize.ts"; import { withExponentialBackoff } from "../util/retry.ts"; import { CloudflareApiError } from "./api-error.ts"; import { extractCloudflareResult } from "./api-response.ts"; import { type CloudflareApi, type CloudflareApiOptions } from "./api.ts"; interface WorkerSubdomainProps extends CloudflareApiOptions { /** * The name of the script to create a subdomain for. */ scriptName: string; /** * The version ID of the worker, if versioning is enabled and the worker is a preview. * * @default undefined */ previewVersionId?: string; /** * Whether to enable previews for the subdomain. * * Must be `false` when the worker has Durable Object bindings. * * @default true */ previewsEnabled?: boolean; /** * Prevents the subdomain from being deleted when the worker is deleted. * * @default false */ retain?: boolean; /** * If true, the subdomain will not be created, but will be retained if it already exists. * This is used for local development. * * @default `false` * @internal */ dev?: boolean; } export interface WorkerSubdomain {} export const WorkerSubdomain = Resource( "cloudflare::WorkerSubdomain", async function ( this: Context, _id: string, props: WorkerSubdomainProps, ) { if (this.phase === "delete") { return this.destroy(); } return {}; }, ); export async function disableWorkerSubdomain( api: CloudflareApi, scriptName: string, ) { await extractCloudflareResult( `disable subdomain for "${scriptName}"`, api.post( `/accounts/${api.accountId}/workers/scripts/${scriptName}/subdomain`, { enabled: false, }, ), ).catch((error) => { if (error.status === 404) { return; } throw error; }); } export async function enableWorkerSubdomain( api: CloudflareApi, scriptName: string, previewsEnabled: boolean = true, ) { await withExponentialBackoff( () => extractCloudflareResult( `enable subdomain for "${scriptName}"`, api.post( `/accounts/${api.accountId}/workers/scripts/${scriptName}/subdomain`, { enabled: true, previews_enabled: previewsEnabled, }, ), ), (error) => error instanceof CloudflareApiError && error.status === 404, 20, 1000, ); } export async function computeWorkerDevDomain( api: CloudflareApi, scriptName: string, ) { return `${scriptName}.${await getAccountSubdomain(api)}.workers.dev`; } export async function getWorkerSubdomain( api: CloudflareApi, scriptName: string, ) { return await extractCloudflareResult( `get subdomain for "${scriptName}"`, api.get( `/accounts/${api.accountId}/workers/scripts/${scriptName}/subdomain`, ), ).catch((error): SubdomainResponse => { if (error.status === 404) { return { enabled: false, previews_enabled: false }; } throw error; }); } interface SubdomainResponse { enabled: boolean; previews_enabled: boolean; } export const getAccountSubdomain = memoize( async (api: CloudflareApi) => { const result = await extractCloudflareResult<{ subdomain: string }>( `get subdomain for account ${api.accountId}`, api.get(`/accounts/${api.accountId}/workers/subdomain`), ); return result.subdomain; }, (api) => api.accountId, ); export async function createWorkerUrl( api: CloudflareApi, scriptName: string, previewVersionId?: string, ) { const subdomain = await getAccountSubdomain(api); const base = `${subdomain}.workers.dev`; let url: string; if (previewVersionId) { url = `https://${previewVersionId.substring(0, 8)}-${scriptName}.${base}`; } else { url = `https://${scriptName}.${base}`; } return url; }