1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { Channel } from "./Channel.ts";
6import type { ChannelGroup } from "./ChannelGroup.ts";
7import type { OriginEndpoint } from "./OriginEndpoint.ts";
8
9/**
10 * Shared scaffolding for AWS Elemental MediaPackage v2 HTTP bindings.
11 *
12 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
13 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the three
14 * builders below. Everything except the operation and the IAM action list
15 * is boilerplate: each builder injects the bound resource's identifying
16 * names (`ChannelGroupName` / `ChannelName` / `OriginEndpointName`) into
17 * every request and grants `actions` on the resource's ARN.
18 *
19 * Harvest-job operations authorize against the *harvest job* ARN, which is
20 * the origin endpoint ARN with a `/harvestJob/{name}` suffix — set
21 * `harvestJobScoped` to grant on that pattern instead of the endpoint ARN.
22 */
23
24/**
25 * Build the impl Effect for a MediaPackage v2 operation scoped to a
26 * {@link ChannelGroup}: the runtime callable injects the group's
27 * `ChannelGroupName` and the deploy-time half grants `actions` on the
28 * group ARN (plus everything beneath it — channels, endpoints, harvest
29 * jobs — for list operations that enumerate child resources).
30 */
31export const makeMediaPackageV2ChannelGroupHttpBinding = <
32 I extends { ChannelGroupName: string },
33 A,
34 E,
35 R,
36>(options: {
37 /** Fully-qualified binding tag, e.g. `AWS.MediaPackageV2.ListHarvestJobs`. */
38 tag: string;
39 /** The distilled operation; `ChannelGroupName` is injected from the group. */
40 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
41 /** IAM actions granted on the group ARN and its child-resource pattern. */
42 actions: readonly string[];
43}) =>
44 Effect.gen(function* () {
45 const op = yield* options.operation;
46
47 return Effect.fn(function* <G extends ChannelGroup>(group: G) {
48 const ChannelGroupName = yield* group.channelGroupName;
49 if (!globalThis.__ALCHEMY_RUNTIME__) {
50 const host = yield* Binding.Host;
51 if (isBindingHost(host)) {
52 yield* host.bind`Allow(${host}, ${options.tag}(${group}))`({
53 policyStatements: [
54 {
55 Effect: "Allow",
56 Action: [...options.actions],
57 Resource: [
58 Output.interpolate`${group.channelGroupArn}`,
59 Output.interpolate`${group.channelGroupArn}/*`,
60 ],
61 },
62 ],
63 });
64 }
65 }
66 return Effect.fn(`${options.tag}(${group.LogicalId})`)(function* (
67 request?: Omit<I, "ChannelGroupName">,
68 ) {
69 return yield* op({
70 ...request,
71 ChannelGroupName: yield* ChannelGroupName,
72 } as I);
73 });
74 });
75 });
76
77/**
78 * Build the impl Effect for a MediaPackage v2 operation scoped to a
79 * {@link Channel}: the runtime callable injects the channel's
80 * `ChannelGroupName` + `ChannelName` and the deploy-time half grants
81 * `actions` on the channel ARN.
82 */
83export const makeMediaPackageV2ChannelHttpBinding = <
84 I extends { ChannelGroupName: string; ChannelName: string },
85 A,
86 E,
87 R,
88>(options: {
89 /** Fully-qualified binding tag, e.g. `AWS.MediaPackageV2.ResetChannelState`. */
90 tag: string;
91 /** The distilled operation; the channel's names are injected. */
92 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
93 /** IAM actions granted on the channel ARN. */
94 actions: readonly string[];
95}) =>
96 Effect.gen(function* () {
97 const op = yield* options.operation;
98
99 return Effect.fn(function* <C extends Channel>(channel: C) {
100 const ChannelGroupName = yield* channel.channelGroupName;
101 const ChannelName = yield* channel.channelName;
102 if (!globalThis.__ALCHEMY_RUNTIME__) {
103 const host = yield* Binding.Host;
104 if (isBindingHost(host)) {
105 yield* host.bind`Allow(${host}, ${options.tag}(${channel}))`({
106 policyStatements: [
107 {
108 Effect: "Allow",
109 Action: [...options.actions],
110 Resource: [Output.interpolate`${channel.channelArn}`],
111 },
112 ],
113 });
114 }
115 }
116 return Effect.fn(`${options.tag}(${channel.LogicalId})`)(function* (
117 request?: Omit<I, "ChannelGroupName" | "ChannelName">,
118 ) {
119 return yield* op({
120 ...request,
121 ChannelGroupName: yield* ChannelGroupName,
122 ChannelName: yield* ChannelName,
123 } as I);
124 });
125 });
126 });
127
128/**
129 * Build the impl Effect for a MediaPackage v2 operation scoped to an
130 * {@link OriginEndpoint}: the runtime callable injects the endpoint's
131 * `ChannelGroupName` + `ChannelName` + `OriginEndpointName` and the
132 * deploy-time half grants `actions` on the endpoint ARN — plus, when
133 * `harvestJobScoped` is set, on the endpoint's harvest-job ARN pattern
134 * (`{endpointArn}/harvestJob/*`) and the parent channel-group ARN:
135 * MediaPackage authorizes `CreateHarvestJob` against the bare channel
136 * group (observed live: "not authorized to perform
137 * mediapackagev2:CreateHarvestJob on resource: arn:…:channelGroup/{g}").
138 */
139export const makeMediaPackageV2OriginEndpointHttpBinding = <
140 I extends {
141 ChannelGroupName: string;
142 ChannelName: string;
143 OriginEndpointName: string;
144 },
145 A,
146 E,
147 R,
148>(options: {
149 /** Fully-qualified binding tag, e.g. `AWS.MediaPackageV2.CreateHarvestJob`. */
150 tag: string;
151 /** The distilled operation; the endpoint's names are injected. */
152 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
153 /** IAM actions granted on the endpoint (or harvest-job pattern) ARN. */
154 actions: readonly string[];
155 /** Additionally grant on `{endpointArn}/harvestJob/*` (harvest-job ops). */
156 harvestJobScoped?: boolean;
157}) =>
158 Effect.gen(function* () {
159 const op = yield* options.operation;
160
161 return Effect.fn(function* <O extends OriginEndpoint>(endpoint: O) {
162 const ChannelGroupName = yield* endpoint.channelGroupName;
163 const ChannelName = yield* endpoint.channelName;
164 const OriginEndpointName = yield* endpoint.originEndpointName;
165 if (!globalThis.__ALCHEMY_RUNTIME__) {
166 const host = yield* Binding.Host;
167 if (isBindingHost(host)) {
168 yield* host.bind`Allow(${host}, ${options.tag}(${endpoint}))`({
169 policyStatements: [
170 {
171 Effect: "Allow",
172 Action: [...options.actions],
173 Resource: [
174 Output.interpolate`${endpoint.originEndpointArn}`,
175 ...(options.harvestJobScoped
176 ? [
177 Output.interpolate`${endpoint.originEndpointArn}/harvestJob/*`,
178 // The endpoint ARN is `…:channelGroup/{g}/channel/{c}/originEndpoint/{e}`;
179 // harvest-job actions authorize against the bare
180 // channel-group ARN prefix.
181 Output.map(
182 endpoint.originEndpointArn,
183 (arn): string => arn.split("/channel/")[0]!,
184 ),
185 ]
186 : []),
187 ],
188 },
189 ],
190 });
191 }
192 }
193 return Effect.fn(`${options.tag}(${endpoint.LogicalId})`)(function* (
194 request?: Omit<
195 I,
196 "ChannelGroupName" | "ChannelName" | "OriginEndpointName"
197 >,
198 ) {
199 return yield* op({
200 ...request,
201 ChannelGroupName: yield* ChannelGroupName,
202 ChannelName: yield* ChannelName,
203 OriginEndpointName: yield* OriginEndpointName,
204 } as I);
205 });
206 });
207 });