1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { IdMappingWorkflow } from "./IdMappingWorkflow.ts";
5import type { MatchingWorkflow } from "./MatchingWorkflow.ts";
6
7/**
8 * Shared scaffolding for Entity Resolution HTTP bindings.
9 *
10 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
11 * a `Layer.effect(Cap, makeWorkflowHttpBinding({ … }))` over the builder
12 * below. Everything except the operation and the IAM action is boilerplate:
13 * all Entity Resolution data-plane operations are scoped to a matching or ID
14 * mapping workflow and inject the bound workflow's name as the request's
15 * `workflowName` field.
16 */
17export const makeWorkflowHttpBinding = <
18 I extends { workflowName: string },
19 A,
20 E,
21 R,
22>(options: {
23 /** Fully-qualified binding tag, e.g. `AWS.EntityResolution.GetMatchId`. */
24 tag: string;
25 /** The distilled operation; `workflowName` is injected from the resource. */
26 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
27 /** IAM actions granted on the workflow ARN. */
28 actions: readonly string[];
29}) =>
30 Effect.gen(function* () {
31 const op = yield* options.operation;
32
33 return Effect.fn(function* (
34 workflow: MatchingWorkflow | IdMappingWorkflow,
35 ) {
36 const WorkflowName = yield* workflow.workflowName;
37 if (!globalThis.__ALCHEMY_RUNTIME__) {
38 const host = yield* Binding.Host;
39 if (isBindingHost(host)) {
40 yield* host.bind`Allow(${host}, ${options.tag}(${workflow}))`({
41 policyStatements: [
42 {
43 Effect: "Allow",
44 Action: [...options.actions],
45 Resource: [workflow.workflowArn],
46 },
47 ],
48 });
49 }
50 }
51 return Effect.fn(`${options.tag}(${workflow.LogicalId})`)(function* (
52 request: Omit<I, "workflowName">,
53 ) {
54 return yield* op({
55 ...request,
56 workflowName: yield* WorkflowName,
57 } as I);
58 });
59 });
60 });