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 { QApp } from "./QApp.ts";
6
7/**
8 * Shared HTTP scaffolding for the Amazon Q Apps runtime bindings.
9 *
10 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
11 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the builders
12 * below. Everything except the operation and the IAM action list is
13 * boilerplate: every Q Apps operation runs against the Q Business
14 * application environment instance of the bound {@link QApp} (its
15 * `instanceId` is injected as the `instance-id` header), and app-keyed
16 * operations additionally inject the bound app's `appId`.
17 *
18 * Two IAM grant shapes:
19 *
20 * - {@link makeQAppHttpBinding} — operations keyed by the bound Q App or one
21 * of its sessions. Granted on the app's ARN and its sub-resources
22 * (`{appArn}`, `{appArn}/*` — Q App session ARNs are children of the app
23 * ARN).
24 * - {@link makeQAppsInstanceHttpBinding} — instance-level operations
25 * (library items, categories, listing, generation). Library-item and
26 * category authorization ARNs are service-assigned and not derivable from
27 * a bound Q App, so these grant on `Resource: ["*"]`.
28 */
29
30/**
31 * Build the impl Effect for a Q Apps operation keyed by the bound
32 * {@link QApp} (or one of its sessions): the runtime callable injects the
33 * app's `instanceId` (and, with `injectAppId`, its `appId`); the deploy-time
34 * half grants `iamActions` on the app ARN and its session sub-resources.
35 */
36export const makeQAppHttpBinding = <
37 I extends { instanceId: string },
38 A,
39 E,
40 R,
41>(options: {
42 /**
43 * Short capability name used in the binding sid and runtime span, e.g.
44 * `"StartQAppSession"`.
45 */
46 capability: string;
47 /** IAM actions granted on the app ARN + `{appArn}/*`. */
48 iamActions: readonly string[];
49 /** The distilled operation; `instanceId` (and `appId`) are injected. */
50 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
51 /** Inject the bound app's `appId` into the request. */
52 injectAppId?: boolean;
53}) =>
54 Effect.gen(function* () {
55 const op = yield* options.operation;
56
57 return Effect.fn(function* (app: QApp) {
58 const instanceId = yield* app.instanceId;
59 const appId = yield* app.appId;
60 if (!globalThis.__ALCHEMY_RUNTIME__) {
61 const host = yield* Binding.Host;
62 if (isBindingHost(host)) {
63 yield* host.bind`Allow(${host}, AWS.QApps.${options.capability}(${app}))`(
64 {
65 policyStatements: [
66 {
67 Effect: "Allow",
68 Action: [...options.iamActions],
69 Resource: [
70 Output.interpolate`${app.appArn}`,
71 Output.interpolate`${app.appArn}/*`,
72 ],
73 },
74 ],
75 },
76 );
77 }
78 }
79 return Effect.fn(`AWS.QApps.${options.capability}(${app.LogicalId})`)(
80 function* (request?: Omit<I, "instanceId" | "appId">) {
81 const input = options.injectAppId
82 ? {
83 ...request,
84 instanceId: yield* instanceId,
85 appId: yield* appId,
86 }
87 : { ...request, instanceId: yield* instanceId };
88 return yield* op(input as I);
89 },
90 );
91 });
92 });
93
94/**
95 * Build the impl Effect for an instance-level Q Apps operation (library
96 * items, categories, listing, generation): the runtime callable injects the
97 * bound {@link QApp}'s `instanceId`. Library-item and category authorization
98 * ARNs are service-assigned and not derivable from a bound Q App, so the
99 * deploy-time half grants `iamActions` on `Resource: ["*"]`.
100 */
101export const makeQAppsInstanceHttpBinding = <
102 I extends { instanceId: string },
103 A,
104 E,
105 R,
106>(options: {
107 /**
108 * Short capability name used in the binding sid and runtime span, e.g.
109 * `"ListLibraryItems"`.
110 */
111 capability: string;
112 /** IAM actions granted on `Resource: ["*"]`. */
113 iamActions: readonly string[];
114 /** The distilled operation; `instanceId` is injected. */
115 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
116}) =>
117 Effect.gen(function* () {
118 const op = yield* options.operation;
119
120 return Effect.fn(function* (app: QApp) {
121 const instanceId = yield* app.instanceId;
122 if (!globalThis.__ALCHEMY_RUNTIME__) {
123 const host = yield* Binding.Host;
124 if (isBindingHost(host)) {
125 yield* host.bind`Allow(${host}, AWS.QApps.${options.capability}(${app}))`(
126 {
127 policyStatements: [
128 {
129 Effect: "Allow",
130 Action: [...options.iamActions],
131 Resource: ["*"],
132 },
133 ],
134 },
135 );
136 }
137 }
138 return Effect.fn(`AWS.QApps.${options.capability}(${app.LogicalId})`)(
139 function* (request?: Omit<I, "instanceId">) {
140 return yield* op({ ...request, instanceId: yield* instanceId } as I);
141 },
142 );
143 });
144 });