import * as Effect from "effect/Effect"; import type * as Redacted from "effect/Redacted"; import type * as HttpClient from "effect/unstable/http/HttpClient"; import type { RuntimeContext } from "../../RuntimeContext.ts"; import { Self } from "../../Self.ts"; import { AccountApiToken } from "../ApiToken/AccountApiToken.ts"; import type { PermissionGroupRef } from "../ApiToken/Common.ts"; import { CloudflareEnvironment } from "../CloudflareEnvironment.ts"; import type { Credentials } from "../Credentials.ts"; import type { Queue } from "./Queue.ts"; import { SendError } from "./QueueTypes.ts"; /** * Injectable auth used by the Queue HTTP client builder. Both the * scoped-token HTTP variant ({@link makeWriteQueueHttpClient}) and the * current-credentials Local variant build this so they share the exact * same request path — only the way credentials reach the SDK op differs. * * - `authorize` runs a raw distilled op (which needs * `Credentials | HttpClient`) and discharges those requirements down to * {@link RuntimeContext}. The HTTP variant provides a minted token; the * Local variant provides the ambient current-credentials context. * - `accountId` resolves the Cloudflare account the queue lives in. */ export interface QueueAuth { authorize: ( eff: Effect.Effect, ) => Effect.Effect; accountId: Effect.Effect; } /** * Shared scaffolding for the HTTP-backed Queue services. * * Creates a scoped {@link AccountApiToken}, binds its `value` / * `accountId` into the host Worker at deploy time, then delegates to * `makeClient` with the bound token and the queue's `queueId`. */ export const makeHttpQueueBinding = (options: { permissionGroups: PermissionGroup[]; makeClient: (token: HttpToken, queueId: Effect.Effect) => Client; }) => Effect.gen(function* () { const Token = yield* AccountApiToken; const self = yield* Self; const env = yield* CloudflareEnvironment; return Effect.fn(function* (queue: Queue) { const { accountId } = yield* env; const token = yield* Token(`${self.LogicalId}Token`); if (!globalThis.__ALCHEMY_RUNTIME__) { yield* token.bind`${queue.LogicalId}`({ policies: [ { effect: "allow", permissionGroups: options.permissionGroups, resources: { [`com.cloudflare.api.account.${accountId}`]: "*", }, }, ], }); } const bound = { value: yield* token.value, accountId: yield* token.accountId, } satisfies HttpToken; const queueId = yield* queue.queueId; return options.makeClient(bound, queueId); }); }); /** Resolve the account and queue id once per operation. */ export const makeQueueHttpScope = ( auth: QueueAuth, queueId: Effect.Effect, ): Effect.Effect => Effect.gen(function* () { const accountId = yield* auth.accountId; const id = yield* queueId; return { accountId, queueId: id }; }); export const toQueueSendError = (error: unknown): SendError => new SendError({ message: typeof error === "object" && error !== null && "message" in error ? String((error as { message: unknown }).message) : "Unknown queue error", cause: error, }); export interface HttpToken { value: Effect.Effect>; accountId: Effect.Effect; } export interface HttpScope { accountId: string; queueId: string; } const QUEUE_HTTP_PERMISSION_GROUPS: PermissionGroupRef[] = [ "Queues Read", "Queues Write", ]; type PermissionGroup = (typeof QUEUE_HTTP_PERMISSION_GROUPS)[number];