1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { Asset } from "./Asset.ts";
5
6/**
7 * Shared HTTP scaffolding for the AWS IoT SiteWise runtime bindings.
8 *
9 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
10 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the builders
11 * below. Everything except the operation, the IAM action, and the request
12 * shaping is boilerplate.
13 */
14
15/**
16 * Build the impl Effect for an IoT SiteWise operation scoped to one
17 * {@link Asset}: the deploy-time half grants `iamActions` on the bound
18 * asset's ARN, and the runtime half injects the asset's service-assigned id
19 * into every request via `prepare`.
20 *
21 * Note: requests that identify the target data stream by `propertyAlias`
22 * instead of `assetId`/`propertyId` authorize against the time-series
23 * resource, which this asset-scoped grant does not cover.
24 */
25export const makeSiteWiseAssetHttpBinding = <I, A, E, R, Req>(options: {
26 /**
27 * Short capability name used in the binding sid and runtime span, e.g.
28 * `"GetAssetPropertyValue"`.
29 */
30 capability: string;
31 /** IAM actions granted on the bound asset's ARN. */
32 iamActions: readonly string[];
33 /** The distilled operation implementing the capability. */
34 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
35 /** Map the public request shape + bound asset id onto the wire request. */
36 prepare: (request: Req, assetId: string) => I;
37}) =>
38 Effect.gen(function* () {
39 const op = yield* options.operation;
40
41 return Effect.fn(function* (asset: Asset) {
42 // Output yields a DEFERRED effect — resolve again per invocation below.
43 const AssetId = yield* asset.assetId;
44 if (!globalThis.__ALCHEMY_RUNTIME__) {
45 const host = yield* Binding.Host;
46 if (isBindingHost(host)) {
47 yield* host.bind`Allow(${host}, AWS.IoTSiteWise.${options.capability}(${asset}))`(
48 {
49 policyStatements: [
50 {
51 Effect: "Allow",
52 Action: [...options.iamActions],
53 Resource: [asset.assetArn],
54 },
55 ],
56 },
57 );
58 }
59 }
60 return Effect.fn(
61 `AWS.IoTSiteWise.${options.capability}(${asset.LogicalId})`,
62 )(function* (request: Req) {
63 const assetId = yield* AssetId;
64 return yield* op(options.prepare(request, assetId));
65 });
66 });
67 });
68
69/**
70 * Build the impl Effect for an account-level IoT SiteWise operation (e.g.
71 * `ExecuteQuery`, which queries across all asset models and assets and is
72 * not scoped to a single resource). Grants `iamActions` on
73 * `Resource: ["*"]`.
74 */
75export const makeSiteWiseAccountHttpBinding = <I, A, E, R>(options: {
76 /** Short capability name used in the binding sid and runtime span. */
77 capability: string;
78 /** IAM actions granted on `Resource: ["*"]`. */
79 iamActions: readonly string[];
80 /** The distilled operation implementing the capability. */
81 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
82}) =>
83 Effect.gen(function* () {
84 const op = yield* options.operation;
85
86 return Effect.fn(function* () {
87 if (!globalThis.__ALCHEMY_RUNTIME__) {
88 const host = yield* Binding.Host;
89 if (isBindingHost(host)) {
90 yield* host.bind`Allow(${host}, AWS.IoTSiteWise.${options.capability}())`(
91 {
92 policyStatements: [
93 {
94 Effect: "Allow",
95 Action: [...options.iamActions],
96 Resource: ["*"],
97 },
98 ],
99 },
100 );
101 }
102 }
103 return Effect.fn(`AWS.IoTSiteWise.${options.capability}`)(function* (
104 request: I,
105 ) {
106 return yield* op(request);
107 });
108 });
109 });