1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { Dashboard } from "./Dashboard.ts";
6import type { DataSet } from "./DataSet.ts";
7
8/**
9 * Shared scaffolding for Amazon QuickSight HTTP bindings.
10 *
11 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
12 * a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the builders
13 * below. Everything except the operation, the scoping resource, and the IAM
14 * action list is boilerplate.
15 *
16 * Every QuickSight API call requires the `AwsAccountId`. The runtime half of
17 * each binding derives it from the bound resource's ARN
18 * (`arn:aws:quicksight:{region}:{account}:{type}/{id}`), so no extra
19 * environment plumbing is needed inside the Lambda.
20 */
21
22/** Extract the AWS account id from a QuickSight resource ARN. */
23export const accountIdFromArn = (arn: string): string => arn.split(":")[4]!;
24
25/**
26 * Build the impl Effect for a QuickSight operation scoped to a
27 * {@link Dashboard}: the deploy-time half grants `actions` on the bound
28 * dashboard's ARN, and the runtime half injects `AwsAccountId` (parsed from
29 * the dashboard ARN) and `DashboardId` into every request.
30 */
31export const makeQuickSightDashboardHttpBinding = <
32 I extends { AwsAccountId: string; DashboardId: string },
33 A,
34 E,
35 R,
36>(options: {
37 /** Fully-qualified binding tag, e.g. `AWS.QuickSight.StartDashboardSnapshotJob`. */
38 tag: string;
39 /** The distilled operation; `AwsAccountId` and `DashboardId` are injected. */
40 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
41 /** IAM actions granted on the dashboard ARN. */
42 actions: readonly string[];
43}) =>
44 Effect.gen(function* () {
45 const op = yield* options.operation;
46
47 return Effect.fn(function* (dashboard: Dashboard) {
48 const DashboardId = yield* dashboard.dashboardId;
49 const Arn = yield* dashboard.arn;
50 if (!globalThis.__ALCHEMY_RUNTIME__) {
51 const host = yield* Binding.Host;
52 if (isBindingHost(host)) {
53 yield* host.bind`Allow(${host}, ${options.tag}(${dashboard}))`({
54 policyStatements: [
55 {
56 Effect: "Allow",
57 Action: [...options.actions],
58 Resource: [dashboard.arn],
59 },
60 ],
61 });
62 }
63 }
64 return Effect.fn(`${options.tag}(${dashboard.LogicalId})`)(function* (
65 request: Omit<I, "AwsAccountId" | "DashboardId">,
66 ) {
67 const arn = yield* Arn;
68 return yield* op({
69 ...request,
70 AwsAccountId: accountIdFromArn(arn),
71 DashboardId: yield* DashboardId,
72 } as I);
73 });
74 });
75 });
76
77/**
78 * Build the impl Effect for a QuickSight operation scoped to a
79 * {@link DataSet}: the deploy-time half grants `actions` on the bound
80 * dataset's ARN and its `…/ingestion/*` sub-resources, and the runtime half
81 * injects `AwsAccountId` (parsed from the dataset ARN) and `DataSetId` into
82 * every request.
83 */
84export const makeQuickSightDataSetHttpBinding = <
85 I extends { AwsAccountId: string; DataSetId: string },
86 A,
87 E,
88 R,
89>(options: {
90 /** Fully-qualified binding tag, e.g. `AWS.QuickSight.CreateIngestion`. */
91 tag: string;
92 /** The distilled operation; `AwsAccountId` and `DataSetId` are injected. */
93 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
94 /** IAM actions granted on the dataset ARN + its ingestion sub-resources. */
95 actions: readonly string[];
96}) =>
97 Effect.gen(function* () {
98 const op = yield* options.operation;
99
100 return Effect.fn(function* (dataSet: DataSet) {
101 const DataSetId = yield* dataSet.dataSetId;
102 const Arn = yield* dataSet.arn;
103 if (!globalThis.__ALCHEMY_RUNTIME__) {
104 const host = yield* Binding.Host;
105 if (isBindingHost(host)) {
106 yield* host.bind`Allow(${host}, ${options.tag}(${dataSet}))`({
107 policyStatements: [
108 {
109 Effect: "Allow",
110 Action: [...options.actions],
111 Resource: [
112 dataSet.arn,
113 Output.interpolate`${dataSet.arn}/ingestion/*`,
114 ],
115 },
116 ],
117 });
118 }
119 }
120 return Effect.fn(`${options.tag}(${dataSet.LogicalId})`)(function* (
121 request?: Omit<I, "AwsAccountId" | "DataSetId">,
122 ) {
123 const arn = yield* Arn;
124 return yield* op({
125 ...request,
126 AwsAccountId: accountIdFromArn(arn),
127 DataSetId: yield* DataSetId,
128 } as I);
129 });
130 });
131 });
132
133/**
134 * Build the impl Effect for a QuickSight embed-URL operation scoped to a
135 * {@link Dashboard}: the deploy-time half grants `actions` on `*` (embed-URL
136 * actions authorize against user/namespace ARNs that are unknown at deploy
137 * time), and the runtime half injects `AwsAccountId` and defaults the
138 * experience configuration to the bound dashboard via `applyDefaults`.
139 */
140export const makeQuickSightEmbedHttpBinding = <
141 I extends { AwsAccountId: string },
142 Req,
143 A,
144 E,
145 R,
146>(options: {
147 /** Fully-qualified binding tag, e.g. `AWS.QuickSight.GenerateEmbedUrlForRegisteredUser`. */
148 tag: string;
149 /** The distilled operation; `AwsAccountId` is injected. */
150 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
151 /** IAM actions granted on `*`. */
152 actions: readonly string[];
153 /** Fill dashboard-derived defaults into the caller's request. */
154 applyDefaults: (
155 request: Req,
156 dashboard: { dashboardId: string; arn: string },
157 ) => Omit<I, "AwsAccountId">;
158}) =>
159 Effect.gen(function* () {
160 const op = yield* options.operation;
161
162 return Effect.fn(function* (dashboard: Dashboard) {
163 const DashboardId = yield* dashboard.dashboardId;
164 const Arn = yield* dashboard.arn;
165 if (!globalThis.__ALCHEMY_RUNTIME__) {
166 const host = yield* Binding.Host;
167 if (isBindingHost(host)) {
168 yield* host.bind`Allow(${host}, ${options.tag}(${dashboard}))`({
169 policyStatements: [
170 {
171 Effect: "Allow",
172 Action: [...options.actions],
173 Resource: ["*"],
174 },
175 ],
176 });
177 }
178 }
179 return Effect.fn(`${options.tag}(${dashboard.LogicalId})`)(function* (
180 request: Req,
181 ) {
182 const arn = yield* Arn;
183 const dashboardId = yield* DashboardId;
184 return yield* op({
185 ...options.applyDefaults(request, { dashboardId, arn }),
186 AwsAccountId: accountIdFromArn(arn),
187 } as I);
188 });
189 });
190 });