1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { Canary } from "./Canary.ts";
5
6/**
7 * Shared scaffolding for CloudWatch Synthetics HTTP bindings.
8 *
9 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
10 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two
11 * builders below. Everything except the operation and the IAM action is
12 * boilerplate:
13 *
14 * - {@link makeSyntheticsCanaryHttpBinding} — operations scoped to one bound
15 * {@link Canary} (`GetCanary`, `GetCanaryRuns`, `StartCanary`,
16 * `StopCanary`). The runtime callable injects the canary's name as the
17 * request's `Name`; the deploy-time half grants `actions` on the canary
18 * ARN.
19 * - {@link makeSyntheticsAccountHttpBinding} — account-level operations
20 * (`DescribeCanariesLastRun`), granted on `*`.
21 */
22
23/**
24 * Build the impl Effect for an operation scoped to a single bound
25 * {@link Canary}. The runtime callable injects the canary's name as the
26 * request's `Name`; the deploy-time half grants `actions` on the canary ARN.
27 */
28export const makeSyntheticsCanaryHttpBinding = <
29 I extends { Name: string },
30 A,
31 E,
32 R,
33>(options: {
34 /** Fully-qualified binding tag, e.g. `AWS.Synthetics.GetCanaryRuns`. */
35 tag: string;
36 /** The distilled operation; `Name` is injected from the resource. */
37 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
38 /** IAM actions granted on the canary ARN. */
39 actions: readonly string[];
40}) =>
41 Effect.gen(function* () {
42 const op = yield* options.operation;
43
44 return Effect.fn(function* (canary: Canary) {
45 const Name = yield* canary.canaryName;
46 if (!globalThis.__ALCHEMY_RUNTIME__) {
47 const host = yield* Binding.Host;
48 if (isBindingHost(host)) {
49 yield* host.bind`Allow(${host}, ${options.tag}(${canary}))`({
50 policyStatements: [
51 {
52 Effect: "Allow",
53 Action: [...options.actions],
54 Resource: [canary.canaryArn],
55 },
56 ],
57 });
58 }
59 }
60 return Effect.fn(`${options.tag}(${canary.LogicalId})`)(function* (
61 request?: Omit<I, "Name">,
62 ) {
63 return yield* op({ ...request, Name: yield* Name } as I);
64 });
65 });
66 });
67
68/**
69 * Build the impl Effect for an account-level Synthetics operation. The
70 * runtime callable passes the caller's request through unchanged; the
71 * deploy-time half grants `actions` on `*` (account-level reads such as
72 * `DescribeCanariesLastRun` are not resource-addressable).
73 */
74export const makeSyntheticsAccountHttpBinding = <I, A, E, R>(options: {
75 /** Fully-qualified binding tag, e.g. `AWS.Synthetics.DescribeCanariesLastRun`. */
76 tag: string;
77 /** The distilled operation, invoked with the caller's request as-is. */
78 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
79 /** IAM actions granted on `*`. */
80 actions: readonly string[];
81}) =>
82 Effect.gen(function* () {
83 const op = yield* options.operation;
84
85 return Effect.fn(function* () {
86 if (!globalThis.__ALCHEMY_RUNTIME__) {
87 const host = yield* Binding.Host;
88 if (isBindingHost(host)) {
89 yield* host.bind`Allow(${host}, ${options.tag}())`({
90 policyStatements: [
91 {
92 Effect: "Allow",
93 Action: [...options.actions],
94 Resource: ["*"],
95 },
96 ],
97 });
98 }
99 }
100 return Effect.fn(options.tag)(function* (request?: I) {
101 return yield* op((request ?? {}) as I);
102 });
103 });
104 });