1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import { isInstance } from "../EC2/Instance.ts";
5import { isBindingHost } from "../Lambda/Function.ts";
6import type { Queue } from "./Queue.ts";
7
8/**
9 * Shared scaffolding for AWS SQS HTTP bindings.
10 *
11 * NOT exported from `index.ts` — every near-identical `{Op}Http.ts` in this
12 * service is a thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of
13 * the builders below. Everything except the operation, the IAM action list,
14 * and the injected identifier is boilerplate. Genuinely-different bindings
15 * (the batched `QueueSink`, the dual-queue `StartMessageMoveTask`) stay
16 * bespoke.
17 */
18
19const grantOnQueue = (tag: string, queue: Queue, actions: readonly string[]) =>
20 Effect.gen(function* () {
21 if (!globalThis.__ALCHEMY_RUNTIME__) {
22 const host = yield* Binding.Host;
23 if (isBindingHost(host) || isInstance(host)) {
24 yield* host.bind`Allow(${host}, ${tag}(${queue}))`({
25 policyStatements: [
26 {
27 Effect: "Allow",
28 Action: [...actions],
29 Resource: [Output.interpolate`${queue.queueArn}`],
30 },
31 ],
32 });
33 }
34 }
35 });
36
37/**
38 * Build the impl Effect for a queue-scoped operation whose request carries
39 * the queue identity as `QueueUrl` (`SendMessage`, `ReceiveMessage`,
40 * `PurgeQueue`, …): the runtime callable injects the bound {@link Queue}'s
41 * URL and the deploy-time half grants `actions` on the queue's ARN.
42 */
43export const makeQueueUrlHttpBinding = <
44 I extends { QueueUrl?: string },
45 A,
46 E,
47 R,
48>(options: {
49 /** Fully-qualified binding tag, e.g. `AWS.SQS.SendMessage`. */
50 tag: string;
51 /** The distilled operation; the queue URL is injected as `QueueUrl`. */
52 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
53 /** IAM actions granted on the queue ARN. */
54 actions: readonly string[];
55}) =>
56 Effect.gen(function* () {
57 const op = yield* options.operation;
58
59 return Effect.fn(function* (queue: Queue) {
60 const QueueUrl = yield* queue.queueUrl;
61 yield* grantOnQueue(options.tag, queue, options.actions);
62 return Effect.fn(`${options.tag}(${queue.LogicalId})`)(function* (
63 request?: Omit<I, "QueueUrl">,
64 ) {
65 return yield* op({
66 ...request,
67 QueueUrl: yield* QueueUrl,
68 } as I);
69 });
70 });
71 });
72
73/**
74 * Build the impl Effect for a dead-letter-queue-scoped message-move-task
75 * operation whose request carries the queue identity as `SourceArn`
76 * (`ListMessageMoveTasks`): the runtime callable injects the bound
77 * {@link Queue}'s ARN and the deploy-time half grants `actions` on it.
78 */
79export const makeQueueArnHttpBinding = <
80 I extends { SourceArn?: string },
81 A,
82 E,
83 R,
84>(options: {
85 /** Fully-qualified binding tag, e.g. `AWS.SQS.ListMessageMoveTasks`. */
86 tag: string;
87 /** The distilled operation; the queue ARN is injected as `SourceArn`. */
88 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
89 /** IAM actions granted on the queue ARN. */
90 actions: readonly string[];
91}) =>
92 Effect.gen(function* () {
93 const op = yield* options.operation;
94
95 return Effect.fn(function* (queue: Queue) {
96 const SourceArn = yield* queue.queueArn;
97 yield* grantOnQueue(options.tag, queue, options.actions);
98 return Effect.fn(`${options.tag}(${queue.LogicalId})`)(function* (
99 request?: Omit<I, "SourceArn">,
100 ) {
101 return yield* op({
102 ...request,
103 SourceArn: yield* SourceArn,
104 } as I);
105 });
106 });
107 });
108
109/**
110 * Build the impl Effect for a queue-scoped operation whose request carries
111 * no queue identity at all (`CancelMessageMoveTask`): the runtime callable
112 * passes the caller's request through unchanged while the deploy-time half
113 * still grants `actions` on the bound {@link Queue}'s ARN.
114 */
115export const makeQueueGrantHttpBinding = <I, A, E, R>(options: {
116 /** Fully-qualified binding tag, e.g. `AWS.SQS.CancelMessageMoveTask`. */
117 tag: string;
118 /** The distilled operation, invoked with the caller's request as-is. */
119 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
120 /** IAM actions granted on the queue ARN. */
121 actions: readonly string[];
122}) =>
123 Effect.gen(function* () {
124 const op = yield* options.operation;
125
126 return Effect.fn(function* (queue: Queue) {
127 yield* grantOnQueue(options.tag, queue, options.actions);
128 return Effect.fn(`${options.tag}(${queue.LogicalId})`)(function* (
129 request: I,
130 ) {
131 return yield* op(request);
132 });
133 });
134 });