1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { ManagedThing } from "./ManagedThing.ts";
5
6/**
7 * Shared scaffolding for the AWS IoT Managed Integrations HTTP bindings.
8 *
9 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
10 * a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the builders
11 * below. Everything except the operation, the IAM action list, and (for the
12 * thing-scoped builder) the injected identifier field is boilerplate.
13 */
14
15/**
16 * Build the impl Effect for an operation scoped to one {@link ManagedThing}.
17 * The deploy-time half grants `iamActions` on the bound thing's ARN; the
18 * runtime half injects the thing's service-generated id into the request
19 * under `key` (the API is split between `Identifier` and `ManagedThingId`
20 * request fields).
21 */
22export const makeManagedThingHttpBinding = <
23 I extends object,
24 A,
25 E,
26 R,
27 K extends "Identifier" | "ManagedThingId",
28>(options: {
29 /**
30 * Short capability name used in the binding sid and runtime span, e.g.
31 * `"SendManagedThingCommand"`.
32 */
33 capability: string;
34 /** IAM actions granted on the bound managed thing's ARN. */
35 iamActions: readonly string[];
36 /** The distilled operation; the field named `key` is injected. */
37 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
38 /** Request field that carries the bound thing's id. */
39 key: K;
40}) =>
41 Effect.gen(function* () {
42 const op = yield* options.operation;
43
44 return Effect.fn(function* (thing: ManagedThing) {
45 // Output yields a DEFERRED effect — resolve again per invocation below.
46 const ManagedThingId = yield* thing.managedThingId;
47 if (!globalThis.__ALCHEMY_RUNTIME__) {
48 const host = yield* Binding.Host;
49 if (isBindingHost(host)) {
50 yield* host.bind`Allow(${host}, AWS.IoTManagedIntegrations.${options.capability}(${thing}))`(
51 {
52 policyStatements: [
53 {
54 Effect: "Allow",
55 Action: [...options.iamActions],
56 Resource: [thing.managedThingArn],
57 },
58 ],
59 },
60 );
61 }
62 }
63 return Effect.fn(
64 `AWS.IoTManagedIntegrations.${options.capability}(${thing.LogicalId})`,
65 )(function* (request?: Omit<I, K>) {
66 return yield* op({
67 ...request,
68 [options.key]: yield* ManagedThingId,
69 } as I);
70 });
71 });
72 });
73
74/**
75 * Build the impl Effect for an account-level IoT Managed Integrations
76 * operation (device discovery, the schema catalog, the custom endpoint, and
77 * connector events — per the `iotmanagedintegrations` service authorization
78 * reference these authorize account-wide, so the grant is on
79 * `Resource: ["*"]`).
80 */
81export const makeManagedIntegrationsHttpBinding = <
82 I extends object,
83 A,
84 E,
85 R,
86>(options: {
87 /**
88 * Short capability name used in the binding sid and runtime span, e.g.
89 * `"StartDeviceDiscovery"`.
90 */
91 capability: string;
92 /** IAM actions granted on `Resource: ["*"]`. */
93 iamActions: readonly string[];
94 /** The distilled operation implementing the capability. */
95 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
96}) =>
97 Effect.gen(function* () {
98 const op = yield* options.operation;
99
100 return Effect.fn(function* () {
101 if (!globalThis.__ALCHEMY_RUNTIME__) {
102 const host = yield* Binding.Host;
103 if (isBindingHost(host)) {
104 yield* host.bind`Allow(${host}, AWS.IoTManagedIntegrations.${options.capability}())`(
105 {
106 policyStatements: [
107 {
108 Effect: "Allow",
109 Action: [...options.iamActions],
110 Resource: ["*"],
111 },
112 ],
113 },
114 );
115 }
116 }
117 return Effect.fn(`AWS.IoTManagedIntegrations.${options.capability}`)(
118 function* (request?: I) {
119 return yield* op((request ?? {}) as I);
120 },
121 );
122 });
123 });