1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { Stage } from "./Stage.ts";
5
6/**
7 * Shared scaffolding for Amazon IVS Real-Time HTTP bindings.
8 *
9 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
10 * a `Layer.effect(Cap, makeIvsRealtime…HttpBinding({ … }))` over one of the
11 * builders below. Everything except the operation, the IAM action list, and
12 * an optional request-shape mapper is boilerplate.
13 */
14
15/**
16 * Compositions are addressed by server-generated ARNs only known at runtime,
17 * so composition-plane grants use this wildcard.
18 */
19export const COMPOSITION_ARN_WILDCARD = "arn:aws:ivs:*:*:composition/*";
20
21/**
22 * Encoder configurations referenced by a composition's destinations are
23 * runtime data, so StartComposition grants include this wildcard.
24 */
25export const ENCODER_CONFIGURATION_ARN_WILDCARD =
26 "arn:aws:ivs:*:*:encoder-configuration/*";
27
28/**
29 * Storage configurations referenced by a composition's S3 destinations are
30 * runtime data, so StartComposition grants include this wildcard.
31 */
32export const STORAGE_CONFIGURATION_ARN_WILDCARD =
33 "arn:aws:ivs:*:*:storage-configuration/*";
34
35/**
36 * Build the impl Effect for an IVS Real-Time operation scoped to a
37 * {@link Stage}: the deploy-time half grants `actions` on the bound stage's
38 * ARN (plus any `extraResources`), and the runtime half injects the stage's
39 * ARN under `requestKey` (default `stageArn`; `ListParticipantReplicas`
40 * addresses the stage as `sourceStageArn`).
41 *
42 * `prepare` (optional) maps a friendlier public request shape onto the wire
43 * request — e.g. `CreateParticipantToken` converts a `Duration.Input` into
44 * the wire `duration` minutes. It defaults to identity.
45 */
46export const makeIvsRealtimeStageHttpBinding = <
47 I extends { [P in K]: string },
48 A,
49 E,
50 R,
51 K extends string = "stageArn",
52 Req = Omit<I, K>,
53>(options: {
54 /** Fully-qualified binding tag, e.g. `AWS.IVSRealtime.CreateParticipantToken`. */
55 tag: string;
56 /** The distilled operation. */
57 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
58 /** IAM actions granted on the stage ARN. */
59 actions: readonly string[];
60 /** Request field carrying the bound stage's ARN. @default "stageArn" */
61 requestKey?: K;
62 /** Static IAM resources granted in addition to the stage ARN. */
63 extraResources?: readonly string[];
64 /** Map the public request shape to the wire request (defaults to identity). */
65 prepare?: (request: Req) => Omit<I, K>;
66}) =>
67 Effect.gen(function* () {
68 const op = yield* options.operation;
69
70 return Effect.fn(function* (stage: Stage) {
71 // Output yields a DEFERRED effect — resolve again per invocation below.
72 const StageArn = yield* stage.stageArn;
73 if (!globalThis.__ALCHEMY_RUNTIME__) {
74 const host = yield* Binding.Host;
75 if (isBindingHost(host)) {
76 yield* host.bind`Allow(${host}, ${options.tag}(${stage}))`({
77 policyStatements: [
78 {
79 Effect: "Allow",
80 Action: [...options.actions],
81 Resource: [stage.stageArn, ...(options.extraResources ?? [])],
82 },
83 ],
84 });
85 }
86 }
87 return Effect.fn(`${options.tag}(${stage.LogicalId})`)(function* (
88 request?: Req,
89 ) {
90 const stageArn = yield* StageArn;
91 const wire = options.prepare
92 ? options.prepare((request ?? {}) as Req)
93 : ((request ?? {}) as unknown as Omit<I, K>);
94 return yield* op({
95 ...wire,
96 [options.requestKey ?? "stageArn"]: stageArn,
97 } as I);
98 });
99 });
100 });
101
102/**
103 * Build the impl Effect for an IVS Real-Time participant-replication
104 * operation: the runtime callable is bound to a **source** and a
105 * **destination** {@link Stage}, injects both ARNs, and the deploy-time half
106 * grants `actions` on both stage ARNs.
107 */
108export const makeIvsRealtimeReplicationHttpBinding = <
109 I extends { sourceStageArn: string; destinationStageArn: string },
110 A,
111 E,
112 R,
113>(options: {
114 /** Fully-qualified binding tag, e.g. `AWS.IVSRealtime.StartParticipantReplication`. */
115 tag: string;
116 /** The distilled operation; both stage ARNs are injected. */
117 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
118 /** IAM actions granted on both stage ARNs. */
119 actions: readonly string[];
120}) =>
121 Effect.gen(function* () {
122 const op = yield* options.operation;
123
124 return Effect.fn(function* (sourceStage: Stage, destinationStage: Stage) {
125 const SourceStageArn = yield* sourceStage.stageArn;
126 const DestinationStageArn = yield* destinationStage.stageArn;
127 if (!globalThis.__ALCHEMY_RUNTIME__) {
128 const host = yield* Binding.Host;
129 if (isBindingHost(host)) {
130 yield* host.bind`Allow(${host}, ${options.tag}(${sourceStage}, ${destinationStage}))`(
131 {
132 policyStatements: [
133 {
134 Effect: "Allow",
135 Action: [...options.actions],
136 Resource: [sourceStage.stageArn, destinationStage.stageArn],
137 },
138 ],
139 },
140 );
141 }
142 }
143 return Effect.fn(
144 `${options.tag}(${sourceStage.LogicalId}, ${destinationStage.LogicalId})`,
145 )(function* (request: Omit<I, "sourceStageArn" | "destinationStageArn">) {
146 return yield* op({
147 ...request,
148 sourceStageArn: yield* SourceStageArn,
149 destinationStageArn: yield* DestinationStageArn,
150 } as I);
151 });
152 });
153 });
154
155/**
156 * Build the impl Effect for an account-level IVS Real-Time operation
157 * (composition management — compositions are addressed by server-generated
158 * ARNs that are runtime data). The deploy-time half grants `actions` on
159 * `resources` (default `*`).
160 */
161export const makeIvsRealtimeAccountHttpBinding = <I, A, E, R>(options: {
162 /** Fully-qualified binding tag, e.g. `AWS.IVSRealtime.ListCompositions`. */
163 tag: string;
164 /** The distilled operation, invoked with the caller's request as-is. */
165 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
166 /** IAM actions granted. */
167 actions: readonly string[];
168 /**
169 * IAM resources the actions are granted on.
170 * @default ["*"]
171 */
172 resources?: readonly string[];
173}) =>
174 Effect.gen(function* () {
175 const op = yield* options.operation;
176
177 return Effect.fn(function* () {
178 if (!globalThis.__ALCHEMY_RUNTIME__) {
179 const host = yield* Binding.Host;
180 if (isBindingHost(host)) {
181 yield* host.bind`Allow(${host}, ${options.tag}())`({
182 policyStatements: [
183 {
184 Effect: "Allow",
185 Action: [...options.actions],
186 Resource: [...(options.resources ?? ["*"])],
187 },
188 ],
189 });
190 }
191 }
192 return Effect.fn(options.tag)(function* (request?: I) {
193 return yield* op((request ?? {}) as I);
194 });
195 });
196 });