1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import type { Role } from "../IAM/Role.ts";
5import { isBindingHost } from "../Lambda/Function.ts";
6
7/**
8 * Shared scaffolding for AWS Transcribe HTTP bindings.
9 *
10 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
11 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two
12 * builders below. Everything except the operation and the IAM action list is
13 * boilerplate: Amazon Transcribe's batch APIs have no resource-level IAM, so
14 * every grant is on `*`. The role-bound operations (`StartCallAnalyticsJob`,
15 * `StartMedicalScribeJob`, `CreateLanguageModel`) additionally inject the
16 * bound data-access role and a scoped `iam:PassRole` grant.
17 */
18
19/**
20 * Build the impl Effect for a Transcribe operation with no bound resource
21 * (start/get/list/delete jobs, vocabulary/filter/category lifecycle, tags).
22 * The deploy-time half grants `actions` on `*` — Transcribe batch IAM
23 * actions do not support resource-level scoping.
24 */
25export const makeTranscribeHttpBinding = <I, A, E, R>(options: {
26 /** Fully-qualified binding tag, e.g. `AWS.Transcribe.GetTranscriptionJob`. */
27 tag: string;
28 /** The distilled operation, invoked with the caller's request as-is. */
29 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
30 /** IAM actions granted on `*`. */
31 actions: readonly string[];
32}) =>
33 Effect.gen(function* () {
34 const op = yield* options.operation;
35
36 return Effect.fn(function* () {
37 if (!globalThis.__ALCHEMY_RUNTIME__) {
38 const host = yield* Binding.Host;
39 if (isBindingHost(host)) {
40 yield* host.bind`Allow(${host}, ${options.tag}())`({
41 policyStatements: [
42 {
43 Effect: "Allow",
44 Action: [...options.actions],
45 Resource: ["*"],
46 },
47 ],
48 });
49 }
50 }
51 return Effect.fn(options.tag)(function* (request?: I) {
52 return yield* op((request ?? {}) as I);
53 });
54 });
55 });
56
57/**
58 * Build the impl Effect for a role-bound Transcribe operation
59 * (`StartCallAnalyticsJob`, `StartMedicalScribeJob`, `CreateLanguageModel`):
60 * the binding is constructed with the **data-access role** (the IAM role
61 * Amazon Transcribe assumes to read your Amazon S3 media/training data and
62 * write results; its trust policy must allow `transcribe.amazonaws.com`).
63 * The role's ARN is injected as `DataAccessRoleArn` on every runtime request
64 * and the deploy-time half grants `actions` on `*` plus `iam:PassRole` on
65 * the role — without the PassRole grant, the operation fails only at runtime
66 * with an AccessDenied.
67 */
68export const makeTranscribeRoleHttpBinding = <
69 I extends { DataAccessRoleArn?: string },
70 A,
71 E,
72 R,
73>(options: {
74 /** Fully-qualified binding tag, e.g. `AWS.Transcribe.StartMedicalScribeJob`. */
75 tag: string;
76 /** The distilled operation; `DataAccessRoleArn` is injected from the role. */
77 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
78 /** IAM actions granted on `*`. */
79 actions: readonly string[];
80}) =>
81 Effect.gen(function* () {
82 const op = yield* options.operation;
83
84 return Effect.fn(function* <Rl extends Role>(dataAccessRole: Rl) {
85 const RoleArn = yield* dataAccessRole.roleArn;
86 if (!globalThis.__ALCHEMY_RUNTIME__) {
87 const host = yield* Binding.Host;
88 if (isBindingHost(host)) {
89 yield* host.bind`Allow(${host}, ${options.tag}(${dataAccessRole}))`({
90 policyStatements: [
91 {
92 Effect: "Allow",
93 Action: [...options.actions],
94 Resource: ["*"],
95 },
96 // CRITICAL: without iam:PassRole on the data-access role, the
97 // operation fails only at runtime with an AccessDenied.
98 {
99 Effect: "Allow",
100 Action: ["iam:PassRole"],
101 Resource: [Output.interpolate`${dataAccessRole.roleArn}`],
102 Condition: {
103 StringEquals: {
104 "iam:PassedToService": "transcribe.amazonaws.com",
105 },
106 },
107 },
108 ],
109 });
110 }
111 }
112 return Effect.fn(`${options.tag}(${dataAccessRole.LogicalId})`)(
113 function* (
114 request: Omit<I, "DataAccessRoleArn"> & {
115 DataAccessRoleArn?: string;
116 },
117 ) {
118 return yield* op({
119 ...request,
120 DataAccessRoleArn: request.DataAccessRoleArn ?? (yield* RoleArn),
121 } as I);
122 },
123 );
124 });
125 });