1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { ComponentVersion } from "./ComponentVersion.ts";
5import type { Deployment } from "./Deployment.ts";
6
7/**
8 * Shared scaffolding for IoT Greengrass V2 HTTP bindings.
9 *
10 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
11 * a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the three
12 * builders below. Everything except the operation and the IAM action list is
13 * boilerplate.
14 */
15
16/**
17 * Build the impl Effect for a Greengrass V2 operation scoped to a
18 * {@link ComponentVersion}: the deploy-time half grants `actions` on the
19 * bound component version's ARN, and the runtime half injects the component
20 * version's `arn` into every request.
21 */
22export const makeGreengrassComponentHttpBinding = <
23 I extends { arn: string },
24 A,
25 E,
26 R,
27>(options: {
28 /** Fully-qualified binding tag, e.g. `AWS.GreengrassV2.GetComponent`. */
29 tag: string;
30 /** The distilled operation. */
31 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
32 /** IAM actions granted on the component version ARN. */
33 actions: readonly string[];
34}) =>
35 Effect.gen(function* () {
36 const op = yield* options.operation;
37
38 return Effect.fn(function* (component: ComponentVersion) {
39 const Arn = yield* component.arn;
40 if (!globalThis.__ALCHEMY_RUNTIME__) {
41 const host = yield* Binding.Host;
42 if (isBindingHost(host)) {
43 yield* host.bind`Allow(${host}, ${options.tag}(${component}))`({
44 policyStatements: [
45 {
46 Effect: "Allow",
47 Action: [...options.actions],
48 Resource: [component.arn],
49 },
50 ],
51 });
52 }
53 }
54 return Effect.fn(`${options.tag}(${component.LogicalId})`)(function* (
55 request?: Omit<I, "arn">,
56 ) {
57 const arn = yield* Arn;
58 return yield* op({ ...request, arn } as I);
59 });
60 });
61 });
62
63/**
64 * Build the impl Effect for a Greengrass V2 operation scoped to a
65 * {@link Deployment}: the deploy-time half grants `actions` on the bound
66 * deployment's ARN, and the runtime half injects the `deploymentId` into
67 * every request.
68 */
69export const makeGreengrassDeploymentHttpBinding = <
70 I extends { deploymentId: string },
71 A,
72 E,
73 R,
74>(options: {
75 /** Fully-qualified binding tag, e.g. `AWS.GreengrassV2.GetDeployment`. */
76 tag: string;
77 /** The distilled operation. */
78 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
79 /** IAM actions granted on the deployment ARN. */
80 actions: readonly string[];
81 /**
82 * Dependent IAM actions granted on `*`. Greengrass V2 deployments are
83 * implemented on IoT Jobs and resolve their thing/thing-group targets with
84 * the caller's credentials, so deployment-plane operations require
85 * `iot:DescribeJob`/`iot:DescribeThing`-family permissions (see the
86 * "dependent actions" column of the service authorization reference).
87 * These cannot be scoped to the deployment ARN — the backing job and
88 * target thing have their own ARNs.
89 */
90 dependentActions?: readonly string[];
91}) =>
92 Effect.gen(function* () {
93 const op = yield* options.operation;
94
95 return Effect.fn(function* (deployment: Deployment) {
96 const DeploymentId = yield* deployment.deploymentId;
97 if (!globalThis.__ALCHEMY_RUNTIME__) {
98 const host = yield* Binding.Host;
99 if (isBindingHost(host)) {
100 yield* host.bind`Allow(${host}, ${options.tag}(${deployment}))`({
101 policyStatements: [
102 {
103 Effect: "Allow",
104 Action: [...options.actions],
105 Resource: [deployment.deploymentArn],
106 },
107 ...(options.dependentActions !== undefined &&
108 options.dependentActions.length > 0
109 ? [
110 {
111 Effect: "Allow" as const,
112 Action: [...options.dependentActions],
113 Resource: ["*"],
114 },
115 ]
116 : []),
117 ],
118 });
119 }
120 }
121 return Effect.fn(`${options.tag}(${deployment.LogicalId})`)(function* (
122 request?: Omit<I, "deploymentId">,
123 ) {
124 const deploymentId = yield* DeploymentId;
125 return yield* op({ ...request, deploymentId } as I);
126 });
127 });
128 });
129
130/**
131 * Build the impl Effect for an account-level Greengrass V2 operation (listing
132 * components/deployments/core devices, core-device data-plane calls keyed by
133 * a thing name the caller supplies at runtime). The deploy-time half grants
134 * `actions` on `*` — core devices register themselves and are not modeled as
135 * Alchemy resources, so there is no resource ARN to scope down to.
136 */
137export const makeGreengrassAccountHttpBinding = <I, A, E, R>(options: {
138 /** Fully-qualified binding tag, e.g. `AWS.GreengrassV2.ListComponents`. */
139 tag: string;
140 /** The distilled operation, invoked with the caller's request as-is. */
141 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
142 /** IAM actions granted on `*`. */
143 actions: readonly string[];
144}) =>
145 Effect.gen(function* () {
146 const op = yield* options.operation;
147
148 return Effect.fn(function* () {
149 if (!globalThis.__ALCHEMY_RUNTIME__) {
150 const host = yield* Binding.Host;
151 if (isBindingHost(host)) {
152 yield* host.bind`Allow(${host}, ${options.tag}())`({
153 policyStatements: [
154 {
155 Effect: "Allow",
156 Action: [...options.actions],
157 Resource: ["*"],
158 },
159 ],
160 });
161 }
162 }
163 return Effect.fn(options.tag)(function* (request?: I) {
164 return yield* op((request ?? {}) as I);
165 });
166 });
167 });