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 { Adapter } from "./Adapter.ts";
6
7/**
8 * Shared HTTP scaffolding for the Textract 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 two
12 * builders below. Everything except the operation and the IAM action is
13 * boilerplate.
14 *
15 * Textract's document analysis actions (sync `Analyze*`/`Detect*` and the
16 * async `Start*`/`Get*` job APIs) have no resource-level IAM, so the
17 * account-level builder grants on `*`. The adapter management actions
18 * authorize on the adapter / adapter-version resource types, which use
19 * Textract's nonstandard leading-slash ARN path
20 * (`arn:aws:textract:{region}:{account}:/adapters/{adapterId}`), so the
21 * adapter-scoped builder grants on the bound adapter's ARN and its
22 * `/versions/*` children.
23 */
24export const makeTextractHttpBinding = <I extends object, A, E, R>(options: {
25 /**
26 * Short capability name used in the binding sid and runtime span, e.g.
27 * `"AnalyzeDocument"`.
28 */
29 capability: string;
30 /** IAM actions granted on `Resource: ["*"]`. */
31 iamActions: readonly string[];
32 /** The distilled operation implementing the capability. */
33 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
34}) =>
35 Effect.gen(function* () {
36 const op = yield* options.operation;
37
38 return Effect.fn(function* () {
39 if (!globalThis.__ALCHEMY_RUNTIME__) {
40 const host = yield* Binding.Host;
41 if (isBindingHost(host)) {
42 yield* host.bind`Allow(${host}, AWS.Textract.${options.capability}())`(
43 {
44 policyStatements: [
45 {
46 Effect: "Allow",
47 Action: [...options.iamActions],
48 Resource: ["*"],
49 },
50 ],
51 },
52 );
53 }
54 }
55 return Effect.fn(`AWS.Textract.${options.capability}`)(function* (
56 request?: I,
57 ) {
58 return yield* op((request ?? {}) as I);
59 });
60 });
61 });
62
63/**
64 * Build the impl Effect for an adapter-scoped Textract operation: the
65 * binding is constructed with an {@link Adapter}, the deploy-time half
66 * grants `iamActions` on the adapter's ARN (and its `/versions/*`
67 * children), and the runtime callable injects the adapter's `AdapterId`
68 * into every request.
69 */
70export const makeTextractAdapterHttpBinding = <
71 I extends { AdapterId?: string },
72 A,
73 E,
74 R,
75>(options: {
76 /** Short capability name, e.g. `"GetAdapter"`. */
77 capability: string;
78 /** IAM actions granted on the adapter ARN + `/versions/*`. */
79 iamActions: readonly string[];
80 /** The distilled operation; `AdapterId` is injected from the adapter. */
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* <Ad extends Adapter>(adapter: Ad) {
87 const AdapterId = yield* adapter.adapterId;
88 if (!globalThis.__ALCHEMY_RUNTIME__) {
89 const host = yield* Binding.Host;
90 if (isBindingHost(host)) {
91 yield* host.bind`Allow(${host}, AWS.Textract.${options.capability}(${adapter}))`(
92 {
93 policyStatements: [
94 {
95 Effect: "Allow",
96 Action: [...options.iamActions],
97 Resource: [
98 Output.interpolate`${adapter.adapterArn}`,
99 Output.interpolate`${adapter.adapterArn}/versions/*`,
100 ],
101 },
102 ],
103 },
104 );
105 }
106 }
107 return Effect.fn(
108 `AWS.Textract.${options.capability}(${adapter.LogicalId})`,
109 )(function* (request?: Omit<I, "AdapterId">) {
110 return yield* op({
111 ...(request ?? {}),
112 AdapterId: yield* AdapterId,
113 } as I);
114 });
115 });
116 });