import type { CloudflareOpContext } from "@distilled.cloud/cloudflare"; import { Forbidden, Unauthorized } from "@distilled.cloud/cloudflare/Errors"; import * as Effect from "effect/Effect"; import { cloudflare } from "./cloudflare-sdk.ts"; import { AccountDenied, ReauthorizationRequired, TemporarilyUnavailable, } from "./deployment-errors.ts"; import { object } from "./deployment-values.ts"; import { bodyBytes, withResponse } from "../server/http.ts"; export class CloudflareAccount { constructor( private readonly accessToken: string, readonly accountId: string, private readonly network: typeof fetch = fetch, ) {} sdk(operation: Effect.Effect) { return cloudflare(operation, this.accessToken, this.network).pipe( Effect.mapError((error) => error instanceof Unauthorized ? new ReauthorizationRequired() : error instanceof Forbidden ? new AccountDenied() : new TemporarilyUnavailable(), ), ); } response( path: string, init: RequestInit, read: (response: Response) => Effect.Effect, ) { const url = `https://api.cloudflare.com/client/v4/accounts/${this.accountId}/${path}`; const headers = new Headers(init.headers); headers.set("Authorization", `Bearer ${this.accessToken}`); return withResponse( this.network, url, { ...init, headers, }, 60_000, new TemporarilyUnavailable(), (response) => Effect.gen(function* () { if (response.status === 401) return yield* new ReauthorizationRequired(); if (response.status === 403) return yield* new AccountDenied(); return yield* read(response); }), ); } request( path: string, { method = "GET", body, allowNotFound = false, }: { method?: "GET" | "POST" | "PUT" | "PATCH"; body?: unknown; allowNotFound?: boolean; } = {}, ) { return this.response( path, { method, ...(body === undefined ? {} : { body: body instanceof FormData ? body : JSON.stringify(body), ...(body instanceof FormData ? {} : { headers: { "Content-Type": "application/json" } }), }), }, (response) => Effect.gen(function* () { if (response.status === 404 && allowNotFound) return null; const bytes = yield* bodyBytes( response, 2 * 1024 * 1024, new TemporarilyUnavailable(), ); const envelope: unknown = yield* Effect.try({ try: () => JSON.parse(new TextDecoder().decode(bytes)), catch: () => new TemporarilyUnavailable(), }); if ( !response.ok || !object(envelope) || envelope.success !== true || !("result" in envelope) ) return yield* new TemporarilyUnavailable(); return envelope.result; }), ); } }