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;
}),
);
}
}