1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { Secret } from "./Secret.ts";
5
6/**
7 * Shared scaffolding for the AWS Secrets Manager HTTP bindings.
8 *
9 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
10 * a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two builders
11 * below. Everything except the operation and the IAM action list is
12 * boilerplate.
13 */
14
15/**
16 * Build the impl Effect for a Secrets Manager operation scoped to a bound
17 * {@link Secret}: the deploy-time half grants `actions` on the secret's ARN,
18 * and the runtime half injects the secret ARN as `SecretId` into every
19 * request.
20 */
21export const makeSecretHttpBinding = <
22 I extends { SecretId?: string },
23 A,
24 E,
25 R,
26>(options: {
27 /** Fully-qualified binding tag, e.g. `AWS.SecretsManager.GetSecretValue`. */
28 tag: string;
29 /** The distilled operation. */
30 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
31 /** IAM actions granted on the bound secret. */
32 actions: readonly `secretsmanager:${string}`[];
33}) =>
34 Effect.gen(function* () {
35 const op = yield* options.operation;
36
37 return Effect.fn(function* (secret: Secret) {
38 const SecretId = yield* secret.secretArn;
39 if (!globalThis.__ALCHEMY_RUNTIME__) {
40 const host = yield* Binding.Host;
41 if (isBindingHost(host)) {
42 yield* host.bind`Allow(${host}, ${options.tag}(${secret}))`({
43 policyStatements: [
44 {
45 Effect: "Allow",
46 Action: [...options.actions],
47 Resource: [secret.secretArn],
48 },
49 ],
50 });
51 }
52 }
53 return Effect.fn(`${options.tag}(${secret.LogicalId})`)(function* (
54 request?: Omit<I, "SecretId">,
55 ) {
56 const secretId = yield* SecretId;
57 return yield* op({ ...request, SecretId: secretId } as I);
58 });
59 });
60 });
61
62/**
63 * Build the impl Effect for an account-level Secrets Manager operation that
64 * is not scoped to a secret (e.g. `secretsmanager:GetRandomPassword`,
65 * `secretsmanager:ListSecrets`). The deploy-time half grants `actions` on
66 * `*`.
67 */
68export const makeSecretsManagerAccountHttpBinding = <I, A, E, R>(options: {
69 /** Fully-qualified binding tag, e.g. `AWS.SecretsManager.ListSecrets`. */
70 tag: string;
71 /** The distilled operation, invoked with the caller's request as-is. */
72 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
73 /** IAM actions granted on `*`. */
74 actions: readonly `secretsmanager:${string}`[];
75}) =>
76 Effect.gen(function* () {
77 const op = yield* options.operation;
78
79 return Effect.fn(function* () {
80 if (!globalThis.__ALCHEMY_RUNTIME__) {
81 const host = yield* Binding.Host;
82 if (isBindingHost(host)) {
83 yield* host.bind`Allow(${host}, ${options.tag}())`({
84 policyStatements: [
85 {
86 Effect: "Allow",
87 Action: [...options.actions],
88 Resource: ["*"],
89 },
90 ],
91 });
92 }
93 }
94 return Effect.fn(options.tag)(function* (request?: I) {
95 return yield* op((request ?? {}) as I);
96 });
97 });
98 });