1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { OptOutList } from "./OptOutList.ts";
5import type { PhoneNumber } from "./PhoneNumber.ts";
6
7/**
8 * Shared scaffolding for AWS End User Messaging SMS (Pinpoint SMS Voice
9 * v2) HTTP bindings.
10 *
11 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service
12 * is a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the three
13 * builders below. Everything except the operation and the IAM action list
14 * is boilerplate: phone-number-scoped bindings inject the bound number's
15 * ARN as the request's `OriginationIdentity` and grant `actions` on that
16 * ARN; opt-out-list-scoped bindings inject the list's ARN as
17 * `OptOutListName`; account-scoped bindings pass the request through and
18 * grant `actions` on `*`.
19 */
20
21/**
22 * Build the impl Effect for an End User Messaging SMS operation scoped to
23 * a {@link PhoneNumber} (`SendTextMessage`, `PutKeyword`, …): the
24 * deploy-time half grants `actions` on the bound number's ARN, and the
25 * runtime half injects the number's ARN into every request as
26 * `OriginationIdentity` (the API accepts a phone number ID or ARN there).
27 */
28export const makeSmsVoicePhoneNumberHttpBinding = <
29 // Optional so the builder accepts SendTextMessage, whose wire
30 // `OriginationIdentity` is optional (account-default routing); the
31 // binding always injects the bound number's ARN regardless.
32 I extends { OriginationIdentity?: string },
33 A,
34 E,
35 R,
36>(options: {
37 /** Fully-qualified binding tag, e.g. `AWS.PinpointSMSVoiceV2.SendTextMessage`. */
38 tag: string;
39 /** The distilled operation; `OriginationIdentity` is injected from the number. */
40 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
41 /** IAM actions granted on the phone number ARN. */
42 actions: readonly string[];
43}) =>
44 Effect.gen(function* () {
45 const op = yield* options.operation;
46
47 return Effect.fn(function* (phoneNumber: PhoneNumber) {
48 // Output yields a DEFERRED effect — resolve again per invocation below.
49 const PhoneNumberArn = yield* phoneNumber.phoneNumberArn;
50 if (!globalThis.__ALCHEMY_RUNTIME__) {
51 const host = yield* Binding.Host;
52 if (isBindingHost(host)) {
53 yield* host.bind`Allow(${host}, ${options.tag}(${phoneNumber}))`({
54 policyStatements: [
55 {
56 Effect: "Allow",
57 Action: [...options.actions],
58 Resource: [phoneNumber.phoneNumberArn],
59 },
60 ],
61 });
62 }
63 }
64 return Effect.fn(`${options.tag}(${phoneNumber.LogicalId})`)(function* (
65 request?: Omit<I, "OriginationIdentity">,
66 ) {
67 const originationIdentity = yield* PhoneNumberArn;
68 return yield* op({
69 ...request,
70 OriginationIdentity: originationIdentity,
71 } as I);
72 });
73 });
74 });
75
76/**
77 * Build the impl Effect for an End User Messaging SMS operation scoped to
78 * an {@link OptOutList} (`PutOptedOutNumber`, `DescribeOptedOutNumbers`,
79 * …): the deploy-time half grants `actions` on the bound list's ARN, and
80 * the runtime half injects the list's ARN into every request as
81 * `OptOutListName` (the API accepts a list name or ARN there).
82 */
83export const makeSmsVoiceOptOutListHttpBinding = <
84 I extends { OptOutListName: string },
85 A,
86 E,
87 R,
88>(options: {
89 /** Fully-qualified binding tag, e.g. `AWS.PinpointSMSVoiceV2.PutOptedOutNumber`. */
90 tag: string;
91 /** The distilled operation; `OptOutListName` is injected from the list. */
92 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
93 /** IAM actions granted on the opt-out list ARN. */
94 actions: readonly string[];
95}) =>
96 Effect.gen(function* () {
97 const op = yield* options.operation;
98
99 return Effect.fn(function* (optOutList: OptOutList) {
100 // Output yields a DEFERRED effect — resolve again per invocation below.
101 const OptOutListArn = yield* optOutList.optOutListArn;
102 if (!globalThis.__ALCHEMY_RUNTIME__) {
103 const host = yield* Binding.Host;
104 if (isBindingHost(host)) {
105 yield* host.bind`Allow(${host}, ${options.tag}(${optOutList}))`({
106 policyStatements: [
107 {
108 Effect: "Allow",
109 Action: [...options.actions],
110 Resource: [optOutList.optOutListArn],
111 },
112 ],
113 });
114 }
115 }
116 return Effect.fn(`${options.tag}(${optOutList.LogicalId})`)(function* (
117 request?: Omit<I, "OptOutListName">,
118 ) {
119 const optOutListName = yield* OptOutListArn;
120 return yield* op({ ...request, OptOutListName: optOutListName } as I);
121 });
122 });
123 });
124
125/**
126 * Build the impl Effect for an account-level End User Messaging SMS
127 * operation (`CarrierLookup`, `PutMessageFeedback`): the deploy-time half
128 * grants `actions` on `*` — these operations act on message IDs or raw
129 * phone numbers, not on a scopeable resource ARN.
130 */
131export const makeSmsVoiceAccountHttpBinding = <I, A, E, R>(options: {
132 /** Fully-qualified binding tag, e.g. `AWS.PinpointSMSVoiceV2.CarrierLookup`. */
133 tag: string;
134 /** The distilled operation, invoked with the caller's request as-is. */
135 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
136 /** IAM actions granted on `*`. */
137 actions: readonly string[];
138}) =>
139 Effect.gen(function* () {
140 const op = yield* options.operation;
141
142 return Effect.fn(function* () {
143 if (!globalThis.__ALCHEMY_RUNTIME__) {
144 const host = yield* Binding.Host;
145 if (isBindingHost(host)) {
146 yield* host.bind`Allow(${host}, ${options.tag}())`({
147 policyStatements: [
148 {
149 Effect: "Allow",
150 Action: [...options.actions],
151 Resource: ["*"],
152 },
153 ],
154 });
155 }
156 }
157 return Effect.fn(options.tag)(function* (request: I) {
158 return yield* op(request);
159 });
160 });
161 });