import type { Credentials } from "@distilled.cloud/aws/Credentials"; import { Region as AwsRegion } from "@distilled.cloud/aws/Region"; import * as Effect from "effect/Effect"; import type * as HttpClient from "effect/unstable/http/HttpClient"; import * as Binding from "../../Binding.ts"; import { isBindingHost } from "../Lambda/Function.ts"; import type { Certificate } from "./Certificate.ts"; /** * Shared HTTP scaffolding for the ACM runtime bindings. * * Every ACM capability follows the same shape — resolve the distilled * operation, register an IAM policy statement on the binding host, and return * a runtime callable that injects the certificate ARN. The only variation is * the operation, the IAM action(s), and whether the binding is scoped to one * certificate or to the whole account, so those are the only inputs. * * All calls are pinned to `us-east-1`, matching where the * {@link Certificate} resource provider requests certificates (the region * required for CloudFront viewer certificates). * * @internal — not exported from `index.ts`. */ const ACM_REGION = "us-east-1" as const; type AcmRequirements = Credentials | AwsRegion | HttpClient.HttpClient; const withAcmRegion = (effect: Effect.Effect) => // `AwsRegion`'s service value is an `Effect` (see // `@distilled.cloud/aws/Region`), so it must be provided as an effect, not // a bare string. effect.pipe(Effect.provideService(AwsRegion, Effect.succeed(ACM_REGION))); export interface AcmHttpBindingConfig { /** * Short capability name used in the binding sid and runtime span, e.g. * `"DescribeCertificate"`. */ capability: string; /** * IAM actions granted to the binding host, e.g. `["acm:GetCertificate"]`. */ iamActions: readonly string[]; /** * The distilled ACM operation implementing the capability. */ operation: Effect.Effect< (input: Req & { CertificateArn: string }) => Effect.Effect, never, AcmRequirements >; } /** * Build the implementation effect for a certificate-scoped ACM capability: * `Layer.effect(Cap, makeAcmCertificateHttpBinding({ ... }))`. * * `Req` infers as the operation's *full* distilled request (inference against * `Req & { CertificateArn: string }` captures the whole source type). The * runtime callable injects the bound certificate's ARN, so its request type * is `Omit` — matching the capability contracts, which * declare `Omit`. */ export const makeAcmCertificateHttpBinding = ( config: AcmHttpBindingConfig, ) => Effect.gen(function* () { const op = yield* withAcmRegion(config.operation); return Effect.fn(function* (certificate: Certificate) { const CertificateArn = yield* certificate.certificateArn; if (!globalThis.__ALCHEMY_RUNTIME__) { const host = yield* Binding.Host; if (isBindingHost(host)) { yield* host.bind`Allow(${host}, AWS.ACM.${config.capability}(${certificate}))`( { policyStatements: [ { Effect: "Allow", Action: [...config.iamActions], Resource: [certificate.certificateArn], }, ], }, ); } } return Effect.fn( `AWS.ACM.${config.capability}(${certificate.LogicalId})`, )(function* (request?: Omit) { // Sound: at instantiation `Req` always contains `CertificateArn: string` // (every certificate-scoped distilled request does), so // `Omit & { CertificateArn: string }` is exactly // `Req & { CertificateArn: string }`. TypeScript cannot prove this for // an unresolved type parameter, hence the precise assertion. // // The region must be pinned HERE, at the call site: the yield-time // snapshot is only a fallback — the calling fiber's ambient Region // (the host Function's own region) wins over it, so pinning only at // the yield would silently route the call to the wrong region. return yield* withAcmRegion( op({ ...request, CertificateArn: yield* CertificateArn, } as Req & { CertificateArn: string }), ); }); }); }); export interface AcmAccountHttpBindingConfig { /** * Short capability name used in the binding sid and runtime span, e.g. * `"ListCertificates"`. */ capability: string; /** * IAM actions granted to the binding host on `Resource: ["*"]` (ACM * account-level operations are not resource-scoped). */ iamActions: readonly string[]; /** * The distilled ACM operation implementing the capability. */ operation: Effect.Effect< (input: Req) => Effect.Effect, never, AcmRequirements >; } /** * Build the implementation effect for an account-level ACM capability (no * certificate argument): `Layer.effect(Cap, makeAcmAccountHttpBinding({ ... }))`. */ export const makeAcmAccountHttpBinding = ( config: AcmAccountHttpBindingConfig, ) => Effect.gen(function* () { const op = yield* withAcmRegion(config.operation); return Effect.fn(function* () { if (!globalThis.__ALCHEMY_RUNTIME__) { const host = yield* Binding.Host; if (isBindingHost(host)) { yield* host.bind`Allow(${host}, AWS.ACM.${config.capability}())`({ policyStatements: [ { Effect: "Allow", Action: [...config.iamActions], Resource: ["*"], }, ], }); } } return Effect.fn(`AWS.ACM.${config.capability}`)(function* ( request?: Req, ) { // Call-site pin — see makeAcmCertificateHttpBinding above. return yield* withAcmRegion(op(request ?? ({} as Req))); }); }); });