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 AWS Service Catalog runtime bindings.
8 *
9 * Service Catalog's end-user (self-service) plane is account-scoped:
10 * which products a caller can see and launch is governed by portfolio
11 * principal associations, not by IAM resource ARNs, and the target
12 * products / provisioned products are discovered per request at runtime
13 * (`searchProducts`, `searchProvisionedProducts`). Every binding is
14 * therefore account-level and grants its action(s) on `Resource: ["*"]`.
15 *
16 * The provisioning writes (`ProvisionProduct`, `UpdateProvisionedProduct`,
17 * `TerminateProvisionedProduct`) additionally grant the CloudFormation
18 * (and template-fetch `s3:GetObject`) actions Service Catalog performs
19 * with the caller's credentials when the product has no launch-role
20 * constraint.
21 *
22 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service
23 * is a `Layer.effect(Cap, makeServiceCatalogHttpBinding({ … }))` over the
24 * builder below. Everything except the operation and the IAM action list
25 * is boilerplate.
26 */
27export const makeServiceCatalogHttpBinding = <I, A, E, R>(options: {
28 /**
29 * Short capability name used in the binding sid and runtime span, e.g.
30 * `"SearchProducts"`.
31 */
32 capability: string;
33 /**
34 * IAM actions granted on `Resource: ["*"]` (access is governed by
35 * portfolio principal associations; targets are chosen per request).
36 */
37 iamActions: readonly string[];
38 /**
39 * The distilled operation implementing the capability.
40 */
41 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
42}) =>
43 Effect.gen(function* () {
44 const op = yield* options.operation;
45
46 return Effect.fn(function* () {
47 if (!globalThis.__ALCHEMY_RUNTIME__) {
48 const host = yield* Binding.Host;
49 if (isBindingHost(host)) {
50 const policyStatements: PolicyStatement[] = [
51 {
52 Effect: "Allow",
53 Action: [...options.iamActions],
54 // Access is governed by portfolio principal associations;
55 // the target products / provisioned products are chosen per
56 // request at runtime.
57 Resource: ["*"],
58 },
59 ];
60 yield* host.bind`Allow(${host}, AWS.ServiceCatalog.${options.capability}())`(
61 {
62 policyStatements,
63 },
64 );
65 }
66 }
67 return Effect.fn(`AWS.ServiceCatalog.${options.capability}`)(function* (
68 request?: I,
69 ) {
70 return yield* op((request ?? {}) as I);
71 });
72 });
73 });