1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import type { Input } from "../../Input.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { AddressList } from "./AddressList.ts";
6import type { Archive } from "./Archive.ts";
7
8/**
9 * Shared scaffolding for SES Mail Manager HTTP bindings.
10 *
11 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
12 * a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the builders
13 * below. Everything except the operation, the injected identifier, and the
14 * IAM action list is boilerplate.
15 */
16
17const registerHostPolicy = Effect.fn(function* (
18 tag: string,
19 resource: AddressList | Archive,
20 arn: Input<string>,
21 actions: readonly string[],
22) {
23 if (!globalThis.__ALCHEMY_RUNTIME__) {
24 const host = yield* Binding.Host;
25 if (isBindingHost(host)) {
26 yield* host.bind`Allow(${host}, ${tag}(${resource}))`({
27 policyStatements: [
28 {
29 Effect: "Allow",
30 Action: [...actions],
31 Resource: [arn],
32 },
33 ],
34 });
35 }
36 }
37});
38
39/**
40 * Build the impl Effect for a Mail Manager operation scoped to an
41 * {@link AddressList} whose request carries `AddressListId`: the deploy-time
42 * half grants `actions` on the bound list's ARN, and the runtime half
43 * injects the list's id into every request.
44 */
45export const makeAddressListHttpBinding = <
46 I extends { AddressListId: string },
47 A,
48 E,
49 R,
50>(options: {
51 /** Fully-qualified binding tag, e.g. `AWS.MailManager.GetMemberOfAddressList`. */
52 tag: string;
53 /** The distilled operation. */
54 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
55 /** IAM actions granted on the address list ARN. */
56 actions: readonly string[];
57}) =>
58 Effect.gen(function* () {
59 const op = yield* options.operation;
60
61 return Effect.fn(function* (list: AddressList) {
62 const AddressListId = yield* list.addressListId;
63 yield* registerHostPolicy(
64 options.tag,
65 list,
66 list.addressListArn,
67 options.actions,
68 );
69 return Effect.fn(`${options.tag}(${list.LogicalId})`)(function* (
70 request: Omit<I, "AddressListId">,
71 ) {
72 const addressListId = yield* AddressListId;
73 return yield* op({ ...request, AddressListId: addressListId } as I);
74 });
75 });
76 });
77
78/**
79 * Build the impl Effect for a Mail Manager import-job operation scoped to an
80 * {@link AddressList} but keyed by `JobId` (the id of a job created against
81 * that list): the deploy-time half grants `actions` on the bound list's ARN;
82 * the runtime half passes the caller's request through unchanged.
83 */
84export const makeAddressListJobHttpBinding = <I, A, E, R>(options: {
85 /** Fully-qualified binding tag, e.g. `AWS.MailManager.StartAddressListImportJob`. */
86 tag: string;
87 /** The distilled operation, invoked with the caller's request as-is. */
88 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
89 /** IAM actions granted on the address list ARN. */
90 actions: readonly string[];
91}) =>
92 Effect.gen(function* () {
93 const op = yield* options.operation;
94
95 return Effect.fn(function* (list: AddressList) {
96 yield* registerHostPolicy(
97 options.tag,
98 list,
99 list.addressListArn,
100 options.actions,
101 );
102 return Effect.fn(`${options.tag}(${list.LogicalId})`)(function* (
103 request: I,
104 ) {
105 return yield* op(request);
106 });
107 });
108 });
109
110/**
111 * Build the impl Effect for a Mail Manager operation scoped to an
112 * {@link Archive} whose request carries `ArchiveId`: the deploy-time half
113 * grants `actions` on the bound archive's ARN, and the runtime half injects
114 * the archive's id into every request.
115 */
116export const makeArchiveHttpBinding = <
117 I extends { ArchiveId: string },
118 A,
119 E,
120 R,
121>(options: {
122 /** Fully-qualified binding tag, e.g. `AWS.MailManager.StartArchiveSearch`. */
123 tag: string;
124 /** The distilled operation. */
125 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
126 /** IAM actions granted on the archive ARN. */
127 actions: readonly string[];
128}) =>
129 Effect.gen(function* () {
130 const op = yield* options.operation;
131
132 return Effect.fn(function* (archive: Archive) {
133 const ArchiveId = yield* archive.archiveId;
134 yield* registerHostPolicy(
135 options.tag,
136 archive,
137 archive.archiveArn,
138 options.actions,
139 );
140 return Effect.fn(`${options.tag}(${archive.LogicalId})`)(function* (
141 request: Omit<I, "ArchiveId">,
142 ) {
143 const archiveId = yield* ArchiveId;
144 return yield* op({ ...request, ArchiveId: archiveId } as I);
145 });
146 });
147 });
148
149/**
150 * Build the impl Effect for a Mail Manager operation scoped to an
151 * {@link Archive} but keyed by a task id (`SearchId`, `ExportId`,
152 * `ArchivedMessageId`): the deploy-time half grants `actions` on the bound
153 * archive's ARN; the runtime half passes the caller's request through
154 * unchanged.
155 */
156export const makeArchiveTaskHttpBinding = <I, A, E, R>(options: {
157 /** Fully-qualified binding tag, e.g. `AWS.MailManager.GetArchiveSearchResults`. */
158 tag: string;
159 /** The distilled operation, invoked with the caller's request as-is. */
160 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
161 /** IAM actions granted on the archive ARN. */
162 actions: readonly string[];
163}) =>
164 Effect.gen(function* () {
165 const op = yield* options.operation;
166
167 return Effect.fn(function* (archive: Archive) {
168 yield* registerHostPolicy(
169 options.tag,
170 archive,
171 archive.archiveArn,
172 options.actions,
173 );
174 return Effect.fn(`${options.tag}(${archive.LogicalId})`)(function* (
175 request: I,
176 ) {
177 return yield* op(request);
178 });
179 });
180 });