1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import { Region } from "../Region.ts";
5import type { PublicRepository } from "./Repository.ts";
6
7/**
8 * Shared HTTP scaffolding for the Amazon ECR Public 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, the IAM action list, and (for the
13 * repository-scoped builder) the injected repository name is boilerplate.
14 *
15 * Amazon ECR Public is a single global registry served exclusively from the
16 * `us-east-1` endpoint, so every operation is resolved with the Region
17 * pinned via {@link pinEcrPublic} — exactly like the resource provider in
18 * `Repository.ts`.
19 *
20 * Per the `ecr-public` service authorization reference, repository-scoped
21 * actions authorize on the repository ARN, while registry-level actions
22 * (`GetAuthorizationToken`, `DescribeRegistries`, `GetRegistryCatalogData`)
23 * support no repository resource type, so the registry builder grants on
24 * `Resource: ["*"]`.
25 */
26
27const US_EAST_1 = "us-east-1";
28
29/** Pin a distilled `ecr-public` effect to the service's home region. */
30export const pinEcrPublic = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
31 effect.pipe(Effect.provideService(Region, Effect.succeed(US_EAST_1)));
32
33/**
34 * Build the impl Effect for an operation scoped to one
35 * {@link PublicRepository}. The runtime callable injects the bound
36 * repository's name as the request's `repositoryName`; the deploy-time half
37 * grants `iamActions` on the repository's ARN.
38 */
39export const makePublicRepositoryHttpBinding = <
40 I extends { repositoryName: string },
41 A,
42 E,
43 R,
44>(options: {
45 /**
46 * Short capability name used in the binding sid and runtime span, e.g.
47 * `"DescribeImages"`.
48 */
49 capability: string;
50 /** IAM actions granted on the repository ARN. */
51 iamActions: readonly string[];
52 /** The distilled operation; `repositoryName` is injected. */
53 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
54}) =>
55 Effect.gen(function* () {
56 const op = yield* pinEcrPublic(options.operation);
57
58 return Effect.fn(function* (repository: PublicRepository) {
59 const RepositoryName = yield* repository.repositoryName;
60 if (!globalThis.__ALCHEMY_RUNTIME__) {
61 const host = yield* Binding.Host;
62 if (isBindingHost(host)) {
63 yield* host.bind`Allow(${host}, AWS.ECRPublic.${options.capability}(${repository}))`(
64 {
65 policyStatements: [
66 {
67 Effect: "Allow",
68 Action: [...options.iamActions],
69 Resource: [repository.repositoryArn],
70 },
71 ],
72 },
73 );
74 }
75 }
76 return Effect.fn(
77 `AWS.ECRPublic.${options.capability}(${repository.LogicalId})`,
78 )(function* (request?: Omit<I, "repositoryName">) {
79 // The region must also be pinned at the call site: the yield-time
80 // snapshot is only a fallback — the calling fiber's ambient Region
81 // (the host Function's own region) wins over it.
82 return yield* pinEcrPublic(
83 op({
84 ...request,
85 repositoryName: yield* RepositoryName,
86 } as unknown as I),
87 );
88 });
89 });
90 });
91
92/**
93 * Build the impl Effect for a registry-level ECR Public operation
94 * (`GetAuthorizationToken`, `DescribeRegistries`, `GetRegistryCatalogData` —
95 * none of which are repository-scoped, so the grant is on
96 * `Resource: ["*"]`).
97 */
98export const makePublicRegistryHttpBinding = <
99 I extends object,
100 A,
101 E,
102 R,
103>(options: {
104 /**
105 * Short capability name used in the binding sid and runtime span, e.g.
106 * `"GetAuthorizationToken"`.
107 */
108 capability: string;
109 /** IAM actions granted on `Resource: ["*"]`. */
110 iamActions: readonly string[];
111 /** The distilled operation implementing the capability. */
112 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
113}) =>
114 Effect.gen(function* () {
115 const op = yield* pinEcrPublic(options.operation);
116
117 return Effect.fn(function* () {
118 if (!globalThis.__ALCHEMY_RUNTIME__) {
119 const host = yield* Binding.Host;
120 if (isBindingHost(host)) {
121 yield* host.bind`Allow(${host}, AWS.ECRPublic.${options.capability}())`(
122 {
123 policyStatements: [
124 {
125 Effect: "Allow",
126 Action: [...options.iamActions],
127 Resource: ["*"],
128 },
129 ],
130 },
131 );
132 }
133 }
134 return Effect.fn(`AWS.ECRPublic.${options.capability}`)(function* (
135 request?: I,
136 ) {
137 // Call-site region pin — see makePublicRepositoryHttpBinding above.
138 return yield* pinEcrPublic(op((request ?? {}) as I));
139 });
140 });
141 });