1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import type { Output as OutputType } from "../../Output.ts";
5import { isBindingHost } from "../Lambda/Function.ts";
6import type { Repository } from "./Repository.ts";
7
8/**
9 * Shared scaffolding for CodeArtifact HTTP bindings.
10 *
11 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
12 * a `Layer.effect(Cap, makeRepositoryHttpBinding({ … }))`. Everything except
13 * the operation, the IAM action list, and the granted ARNs is boilerplate:
14 * the runtime callable injects `domain`, `domainOwner`, and `repository`
15 * from the bound {@link Repository}.
16 */
17
18/** The repository-scoping fields every CodeArtifact package operation takes. */
19interface RepositoryScopedRequest {
20 domain: string;
21 domainOwner?: string;
22 repository: string;
23}
24
25/** The bound repository's own ARN (repository-level actions). */
26export const repositoryArns = (
27 repository: Repository,
28): OutputType<string>[] => [repository.repositoryArn];
29
30/**
31 * ARN matching every package in the bound repository
32 * (`arn:…:package/{domain}/{repository}/*`) — package-level actions like
33 * `codeartifact:DescribePackage` authorize against the package resource,
34 * not the repository.
35 */
36export const packageArns = (repository: Repository): OutputType<string>[] => [
37 repository.repositoryArn.pipe(
38 Output.map((arn) => `${arn.replace(":repository/", ":package/")}/*`),
39 ),
40];
41
42/**
43 * ARNs matching every repository and every package in the bound repository's
44 * domain — for operations like `codeartifact:CopyPackageVersions` that read
45 * from a sibling repository in the same domain.
46 */
47export const domainWideArns = (repository: Repository): OutputType<string>[] =>
48 ["repository", "package"].map((kind) =>
49 repository.repositoryArn.pipe(
50 Output.map((arn) => {
51 const [prefix, rest] = arn.split(":repository/");
52 return `${prefix}:${kind}/${rest!.split("/")[0]}/*`;
53 }),
54 ),
55 );
56
57/**
58 * Build the impl Effect for a repository-scoped CodeArtifact operation. The
59 * deploy-time half grants `actions` on `resources` (default: the repository
60 * ARN); the runtime callable injects the bound repository's `domain`,
61 * `domainOwner`, and `repository` into every request.
62 *
63 * Operations that scope by a differently-named field (e.g.
64 * `CopyPackageVersions`' `destinationRepository`) stay bespoke in their own
65 * `{Op}Http.ts` and reuse just the ARN helpers above.
66 */
67export const makeRepositoryHttpBinding = <
68 I extends { domain: string; domainOwner?: string },
69 A,
70 E,
71 R,
72>(options: {
73 /** Fully-qualified binding tag, e.g. `AWS.CodeArtifact.ListPackages`. */
74 tag: string;
75 /** The distilled operation. */
76 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
77 /** IAM actions granted on `resources`. */
78 actions: readonly string[];
79 /** ARNs the actions are granted on. @default the repository ARN */
80 resources?: (repository: Repository) => OutputType<string>[];
81}) =>
82 Effect.gen(function* () {
83 const op = yield* options.operation;
84
85 return Effect.fn(function* (repository: Repository) {
86 const DomainName = yield* repository.domainName;
87 const DomainOwner = yield* repository.domainOwner;
88 const RepositoryName = yield* repository.repositoryName;
89 if (!globalThis.__ALCHEMY_RUNTIME__) {
90 const host = yield* Binding.Host;
91 if (isBindingHost(host)) {
92 yield* host.bind`Allow(${host}, ${options.tag}(${repository}))`({
93 policyStatements: [
94 {
95 Effect: "Allow",
96 Action: [...options.actions],
97 Resource: (options.resources ?? repositoryArns)(repository),
98 },
99 ],
100 });
101 }
102 }
103 return Effect.fn(`${options.tag}(${repository.LogicalId})`)(function* (
104 request: Omit<I, keyof RepositoryScopedRequest>,
105 ) {
106 const owner = yield* DomainOwner;
107 return yield* op({
108 ...request,
109 domain: yield* DomainName,
110 ...(owner === "" ? {} : { domainOwner: owner }),
111 repository: yield* RepositoryName,
112 } as unknown as I);
113 });
114 });
115 });