1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import type { PolicyStatement } from "../IAM/Policy.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5
6/**
7 * Shared HTTP scaffolding for the Amazon Forecast runtime bindings.
8 *
9 * The runtime-targeted Forecast objects (dataset import jobs, predictors,
10 * forecasts) are created *at runtime* with caller-chosen names, so their ARNs
11 * are unknowable at deploy time — every binding is account-level and grants
12 * its action(s) on `Resource: ["*"]`. The only variation between bindings is
13 * the distilled operation, the IAM action, and whether the operation passes
14 * a data-access role to the service (`CreateDatasetImportJob` /
15 * `CreateAutoPredictor`, which additionally need `iam:PassRole` conditioned
16 * to `forecast.amazonaws.com`).
17 *
18 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
19 * a `Layer.effect(Cap, makeForecastHttpBinding({ … }))`.
20 */
21export const makeForecastHttpBinding = <I, A, E, R>(options: {
22 /**
23 * Short capability name used in the binding sid and runtime span, e.g.
24 * `"QueryForecast"`.
25 */
26 capability: string;
27 /**
28 * IAM actions granted on `Resource: ["*"]` (the target ARNs are created at
29 * runtime and unknowable at deploy time).
30 */
31 iamActions: readonly string[];
32 /**
33 * The distilled operation implementing the capability.
34 */
35 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
36 /**
37 * Grant `iam:PassRole` (conditioned to `forecast.amazonaws.com`) so the
38 * function can hand the service the role it assumes to read S3 training
39 * data or use a customer KMS key. Set on the `Create*` bindings that accept
40 * a `RoleArn`.
41 */
42 passRole?: boolean;
43}) =>
44 Effect.gen(function* () {
45 const op = yield* options.operation;
46
47 return Effect.fn(function* () {
48 if (!globalThis.__ALCHEMY_RUNTIME__) {
49 const host = yield* Binding.Host;
50 if (isBindingHost(host)) {
51 const policyStatements: PolicyStatement[] = [
52 {
53 Effect: "Allow",
54 Action: [...options.iamActions],
55 // Runtime-created Forecast ARNs are unknowable at deploy time.
56 Resource: ["*"],
57 },
58 ];
59 if (options.passRole) {
60 policyStatements.push({
61 Effect: "Allow",
62 Action: ["iam:PassRole"],
63 Resource: ["*"],
64 Condition: {
65 StringEquals: {
66 "iam:PassedToService": "forecast.amazonaws.com",
67 },
68 },
69 });
70 }
71 yield* host.bind`Allow(${host}, AWS.Forecast.${options.capability}())`(
72 { policyStatements },
73 );
74 }
75 }
76 return Effect.fn(`AWS.Forecast.${options.capability}`)(function* (
77 request: I,
78 ) {
79 return yield* op(request);
80 });
81 });
82 });