1import * as SSM from "@distilled.cloud/aws/ssm";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Binding from "../../Binding.ts";
5import * as Output from "../../Output.ts";
6import { isBindingHost } from "../Lambda/Function.ts";
7import {
8 GetParameters,
9 type GetParametersParameters,
10 type GetParametersRequest,
11} from "./GetParameters.ts";
12
13export const GetParametersHttp = Layer.effect(
14 GetParameters,
15 Effect.gen(function* () {
16 const getParameters = yield* SSM.getParameters;
17
18 return Effect.fn(function* (...parameters: GetParametersParameters) {
19 // Resolve name accessors in caller order so the runtime request
20 // preserves the order the user bound the parameters in.
21 const NameAccessors: Effect.Effect<string>[] = [];
22 for (const parameter of parameters) {
23 NameAccessors.push(yield* parameter.parameterName);
24 }
25 if (!globalThis.__ALCHEMY_RUNTIME__) {
26 // Sort by LogicalId so the binding identity (SID + ARN list) is
27 // deterministic regardless of argument order.
28 const sorted = [...parameters].sort((a, b) =>
29 a.LogicalId.localeCompare(b.LogicalId),
30 );
31 const host = yield* Binding.Host;
32 if (isBindingHost(host)) {
33 yield* host.bind`Allow(${host}, AWS.SSM.GetParameters(${sorted}))`({
34 policyStatements: [
35 {
36 Effect: "Allow",
37 Action: ["ssm:GetParameters"],
38 Resource: sorted.map((parameter) => parameter.parameterArn),
39 },
40 {
41 // kms:Decrypt so `WithDecryption: true` works on SecureString
42 // parameters. Parameters without a key (String/StringList)
43 // fall back to their own ARN, which matches no KMS key.
44 Effect: "Allow",
45 Action: ["kms:Decrypt"],
46 Resource: sorted.map((parameter) =>
47 Output.all(parameter.parameterArn, parameter.keyArn).pipe(
48 Output.map(
49 ([parameterArn, keyArn]) => keyArn ?? parameterArn,
50 ),
51 ),
52 ),
53 },
54 ],
55 });
56 }
57 }
58 return Effect.fn(`AWS.SSM.GetParameters(${parameters})`)(function* (
59 request: GetParametersRequest = {},
60 ) {
61 const names = yield* Effect.forEach(
62 NameAccessors,
63 (accessor) => accessor,
64 );
65 return yield* getParameters({
66 ...request,
67 Names: names,
68 });
69 });
70 });
71 }),
72);