1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import type { PolicyStatement } from "../IAM/Policy.ts";
5import { isBindingHost } from "../Lambda/Function.ts";
6import type { Graph } from "./Graph.ts";
7
8/**
9 * Shared scaffolding for the AWS NeptuneGraph (Neptune Analytics) HTTP
10 * bindings.
11 *
12 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
13 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the builders
14 * below. Everything except the operation, the IAM action list, and the
15 * identifier plumbing is boilerplate.
16 */
17
18/**
19 * Graph snapshot ARNs embed a server-generated id that is only known at
20 * runtime, so snapshot-scoped grants use this wildcard.
21 */
22export const SNAPSHOT_ARN_WILDCARD =
23 "arn:aws:neptune-graph:*:*:graph-snapshot/*";
24
25/**
26 * Import-task ARNs embed a server-generated task id that is only known at
27 * runtime, so import-task-scoped grants use this wildcard.
28 */
29export const IMPORT_TASK_ARN_WILDCARD =
30 "arn:aws:neptune-graph:*:*:import-task/*";
31
32/**
33 * Export-task ARNs embed a server-generated task id that is only known at
34 * runtime, so export-task-scoped grants use this wildcard.
35 */
36export const EXPORT_TASK_ARN_WILDCARD =
37 "arn:aws:neptune-graph:*:*:export-task/*";
38
39/**
40 * `iam:PassRole` restricted to Neptune Analytics — required by the
41 * import/export bindings, whose requests hand the service a data-access
42 * role (`roleArn`) chosen at runtime.
43 */
44const passRoleStatement: PolicyStatement = {
45 Effect: "Allow",
46 Action: ["iam:PassRole"],
47 Resource: ["*"],
48 Condition: {
49 StringEquals: {
50 "iam:PassedToService": "neptune-graph.amazonaws.com",
51 },
52 },
53};
54
55/**
56 * Build the impl Effect for a graph-scoped operation: the runtime callable
57 * injects the bound {@link Graph}'s id as `graphIdentifier` and the
58 * deploy-time half grants `actions` on the graph ARN (plus any
59 * `extraResources`).
60 */
61export const makeNeptuneGraphGraphHttpBinding = <
62 I extends { graphIdentifier?: string },
63 A,
64 E,
65 R,
66>(options: {
67 /** Fully-qualified binding tag, e.g. `AWS.NeptuneGraph.StopGraph`. */
68 tag: string;
69 /** The distilled operation; `graphIdentifier` is injected from the graph. */
70 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
71 /** IAM actions granted on the graph ARN. */
72 actions: readonly string[];
73 /** Static IAM resources granted in addition to the graph ARN. */
74 extraResources?: readonly string[];
75 /**
76 * Grant `iam:PassRole` (conditioned to `neptune-graph.amazonaws.com`) so
77 * the function can hand Neptune Analytics the role it assumes to read or
78 * write Amazon S3 data. Set on `StartImportTask` / `StartExportTask`.
79 */
80 passRole?: boolean;
81}) =>
82 Effect.gen(function* () {
83 const op = yield* options.operation;
84
85 return Effect.fn(function* (graph: Graph) {
86 const graphId = yield* graph.graphId;
87 if (!globalThis.__ALCHEMY_RUNTIME__) {
88 const host = yield* Binding.Host;
89 if (isBindingHost(host)) {
90 yield* host.bind`Allow(${host}, ${options.tag}(${graph}))`({
91 policyStatements: [
92 {
93 Effect: "Allow",
94 Action: [...options.actions],
95 Resource: [
96 Output.interpolate`${graph.graphArn}`,
97 ...(options.extraResources ?? []),
98 ],
99 },
100 ...(options.passRole ? [passRoleStatement] : []),
101 ],
102 });
103 }
104 }
105 return Effect.fn(`${options.tag}(${graph.LogicalId})`)(function* (
106 request?: Omit<I, "graphIdentifier">,
107 ) {
108 return yield* op({
109 ...request,
110 graphIdentifier: yield* graphId,
111 } as I);
112 });
113 });
114 });
115
116/**
117 * Build the impl Effect for an account-level operation (snapshot reads and
118 * deletes, import/export task polling and cancellation). The deploy-time
119 * half grants `actions` on `resources` (default `*`) — these operations
120 * address snapshots and tasks by identifiers that are runtime data.
121 */
122export const makeNeptuneGraphAccountHttpBinding = <I, A, E, R>(options: {
123 /** Fully-qualified binding tag, e.g. `AWS.NeptuneGraph.GetImportTask`. */
124 tag: string;
125 /** The distilled operation, invoked with the caller's request as-is. */
126 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
127 /** IAM actions granted. */
128 actions: readonly string[];
129 /**
130 * IAM resources the actions are granted on.
131 * @default ["*"]
132 */
133 resources?: readonly string[];
134}) =>
135 Effect.gen(function* () {
136 const op = yield* options.operation;
137
138 return Effect.fn(function* () {
139 if (!globalThis.__ALCHEMY_RUNTIME__) {
140 const host = yield* Binding.Host;
141 if (isBindingHost(host)) {
142 yield* host.bind`Allow(${host}, ${options.tag}())`({
143 policyStatements: [
144 {
145 Effect: "Allow",
146 Action: [...options.actions],
147 Resource: [...(options.resources ?? ["*"])],
148 },
149 ],
150 });
151 }
152 }
153 return Effect.fn(options.tag)(function* (request?: I) {
154 return yield* op((request ?? {}) as I);
155 });
156 });
157 });