1/**
2 * Shared scaffolding for the Incident Manager Contacts (SSM Contacts) HTTP
3 * bindings.
4 *
5 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
6 * a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the builders
7 * below. Everything except the operation and the IAM action list is
8 * boilerplate:
9 *
10 * - Contact-scoped operations (`ssm-contacts:StartEngagement`,
11 * `ssm-contacts:ListPagesByContact`) inject the bound {@link Contact}'s
12 * ARN as the request's `ContactId` and are granted on the contact ARN.
13 * - Channel-scoped operations (`ssm-contacts:ActivateContactChannel`, …)
14 * inject the bound {@link ContactChannel}'s ARN as `ContactChannelId` and
15 * are granted on the channel ARN.
16 * - Rotation-scoped operations (`ssm-contacts:ListRotationShifts`, rotation
17 * overrides) inject the bound {@link Rotation}'s ARN as `RotationId` and
18 * are granted on the rotation ARN.
19 * - Account-level operations (engagement / page reads and controls) take
20 * the caller's request as-is and are granted on `*` — engagement and page
21 * ARNs are minted at runtime and cannot be scoped statically.
22 */
23import * as Effect from "effect/Effect";
24import * as Binding from "../../Binding.ts";
25import { isBindingHost } from "../Lambda/Function.ts";
26import type { Contact } from "./Contact.ts";
27import type { ContactChannel } from "./ContactChannel.ts";
28import type { Rotation } from "./Rotation.ts";
29
30/**
31 * Build the impl Effect for an SSM Contacts operation scoped to a
32 * {@link Contact}: the deploy-time half grants `actions` on the bound
33 * contact's ARN, and the runtime half injects the contact's ARN as the
34 * request's `ContactId` field.
35 */
36export const makeContactHttpBinding = <
37 I extends { ContactId: string },
38 A,
39 E,
40 R,
41>(options: {
42 /** Fully-qualified binding tag, e.g. `AWS.SSMContacts.StartEngagement`. */
43 tag: string;
44 /** The distilled operation; `ContactId` is injected from the contact. */
45 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
46 /** IAM actions granted on the contact ARN. */
47 actions: readonly string[];
48}) =>
49 Effect.gen(function* () {
50 const op = yield* options.operation;
51
52 return Effect.fn(function* (contact: Contact) {
53 const ContactArn = yield* contact.contactArn;
54 if (!globalThis.__ALCHEMY_RUNTIME__) {
55 const host = yield* Binding.Host;
56 if (isBindingHost(host)) {
57 yield* host.bind`Allow(${host}, ${options.tag}(${contact}))`({
58 policyStatements: [
59 {
60 Effect: "Allow",
61 Action: [...options.actions],
62 Resource: [contact.contactArn],
63 },
64 ],
65 });
66 }
67 }
68 return Effect.fn(`${options.tag}(${contact.LogicalId})`)(function* (
69 request?: Omit<I, "ContactId">,
70 ) {
71 return yield* op({
72 ...request,
73 ContactId: yield* ContactArn,
74 } as I);
75 });
76 });
77 });
78
79/**
80 * Build the impl Effect for an SSM Contacts operation scoped to a
81 * {@link ContactChannel}: the deploy-time half grants `actions` on the bound
82 * channel's ARN, and the runtime half injects the channel's ARN as the
83 * request's `ContactChannelId` field.
84 */
85export const makeContactChannelHttpBinding = <
86 I extends { ContactChannelId: string },
87 A,
88 E,
89 R,
90>(options: {
91 /** Fully-qualified binding tag, e.g. `AWS.SSMContacts.SendActivationCode`. */
92 tag: string;
93 /** The distilled operation; `ContactChannelId` is injected from the channel. */
94 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
95 /** IAM actions granted on the contact channel ARN. */
96 actions: readonly string[];
97}) =>
98 Effect.gen(function* () {
99 const op = yield* options.operation;
100
101 return Effect.fn(function* (channel: ContactChannel) {
102 const ChannelArn = yield* channel.contactChannelArn;
103 if (!globalThis.__ALCHEMY_RUNTIME__) {
104 const host = yield* Binding.Host;
105 if (isBindingHost(host)) {
106 yield* host.bind`Allow(${host}, ${options.tag}(${channel}))`({
107 policyStatements: [
108 {
109 Effect: "Allow",
110 Action: [...options.actions],
111 Resource: [channel.contactChannelArn],
112 },
113 ],
114 });
115 }
116 }
117 return Effect.fn(`${options.tag}(${channel.LogicalId})`)(function* (
118 request?: Omit<I, "ContactChannelId">,
119 ) {
120 return yield* op({
121 ...request,
122 ContactChannelId: yield* ChannelArn,
123 } as I);
124 });
125 });
126 });
127
128/**
129 * Build the impl Effect for an SSM Contacts operation scoped to a
130 * {@link Rotation}: the deploy-time half grants `actions` on the bound
131 * rotation's ARN, and the runtime half injects the rotation's ARN as the
132 * request's `RotationId` field.
133 */
134export const makeRotationHttpBinding = <
135 I extends { RotationId: string },
136 A,
137 E,
138 R,
139>(options: {
140 /** Fully-qualified binding tag, e.g. `AWS.SSMContacts.ListRotationShifts`. */
141 tag: string;
142 /** The distilled operation; `RotationId` is injected from the rotation. */
143 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
144 /** IAM actions granted on the rotation ARN. */
145 actions: readonly string[];
146}) =>
147 Effect.gen(function* () {
148 const op = yield* options.operation;
149
150 return Effect.fn(function* (rotation: Rotation) {
151 const RotationArn = yield* rotation.rotationArn;
152 if (!globalThis.__ALCHEMY_RUNTIME__) {
153 const host = yield* Binding.Host;
154 if (isBindingHost(host)) {
155 yield* host.bind`Allow(${host}, ${options.tag}(${rotation}))`({
156 policyStatements: [
157 {
158 Effect: "Allow",
159 Action: [...options.actions],
160 Resource: [rotation.rotationArn],
161 },
162 ],
163 });
164 }
165 }
166 return Effect.fn(`${options.tag}(${rotation.LogicalId})`)(function* (
167 request?: Omit<I, "RotationId">,
168 ) {
169 return yield* op({
170 ...request,
171 RotationId: yield* RotationArn,
172 } as I);
173 });
174 });
175 });
176
177/**
178 * Build the impl Effect for an account-level SSM Contacts operation
179 * (engagement and page reads/controls). The deploy-time half grants
180 * `actions` on `*` — engagement and page ARNs embed runtime-generated ids
181 * and cannot be scoped to a deployed resource.
182 */
183export const makeAccountHttpBinding = <I, A, E, R>(options: {
184 /** Fully-qualified binding tag, e.g. `AWS.SSMContacts.StopEngagement`. */
185 tag: string;
186 /** The distilled operation, invoked with the caller's request as-is. */
187 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
188 /** IAM actions granted on `*`. */
189 actions: readonly string[];
190}) =>
191 Effect.gen(function* () {
192 const op = yield* options.operation;
193
194 return Effect.fn(function* () {
195 if (!globalThis.__ALCHEMY_RUNTIME__) {
196 const host = yield* Binding.Host;
197 if (isBindingHost(host)) {
198 yield* host.bind`Allow(${host}, ${options.tag}())`({
199 policyStatements: [
200 {
201 Effect: "Allow",
202 Action: [...options.actions],
203 Resource: ["*"],
204 },
205 ],
206 });
207 }
208 }
209 return Effect.fn(options.tag)(function* (request?: I) {
210 return yield* op((request ?? {}) as I);
211 });
212 });
213 });