1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { GeofenceCollection } from "./GeofenceCollection.ts";
6import type { Map as LocationMap } from "./Map.ts";
7import type { PlaceIndex } from "./PlaceIndex.ts";
8import type { RouteCalculator } from "./RouteCalculator.ts";
9import type { Tracker } from "./Tracker.ts";
10
11/**
12 * Shared scaffolding for the Amazon Location Service runtime bindings.
13 *
14 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
15 * thin `Layer.effect(Cap, makeLocation…HttpBinding({ … }))` over one of the
16 * builders below. Everything except the operation and the IAM action list is
17 * boilerplate: each data-plane operation is scoped to exactly one Location
18 * resource (tracker, geofence collection, place index, route calculator, or
19 * map), whose physical name is injected into the request and whose ARN
20 * receives the grant. Batch metadata jobs are account-scoped.
21 */
22
23/**
24 * Build the impl Effect for a tracker-scoped Location operation: the runtime
25 * callable injects the bound {@link Tracker}'s name as `TrackerName` and the
26 * deploy-time half grants `actions` on the tracker ARN.
27 */
28export const makeLocationTrackerHttpBinding = <
29 I extends { TrackerName: string },
30 A,
31 E,
32 R,
33>(options: {
34 /** Fully-qualified binding tag, e.g. `AWS.Location.GetDevicePosition`. */
35 tag: string;
36 /** The distilled operation; `TrackerName` is injected from the tracker. */
37 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
38 /** IAM actions granted on the tracker ARN. */
39 actions: readonly string[];
40}) =>
41 Effect.gen(function* () {
42 const op = yield* options.operation;
43
44 return Effect.fn(function* (tracker: Tracker) {
45 const TrackerName = yield* tracker.trackerName;
46 if (!globalThis.__ALCHEMY_RUNTIME__) {
47 const host = yield* Binding.Host;
48 if (isBindingHost(host)) {
49 yield* host.bind`Allow(${host}, ${options.tag}(${tracker}))`({
50 policyStatements: [
51 {
52 Effect: "Allow",
53 Action: [...options.actions],
54 Resource: [Output.interpolate`${tracker.trackerArn}`],
55 },
56 ],
57 });
58 }
59 }
60 return Effect.fn(`${options.tag}(${tracker.LogicalId})`)(function* (
61 request?: Omit<I, "TrackerName">,
62 ) {
63 return yield* op({
64 ...request,
65 TrackerName: yield* TrackerName,
66 } as I);
67 });
68 });
69 });
70
71/**
72 * Build the impl Effect for a geofence-collection-scoped Location operation:
73 * the runtime callable injects the bound {@link GeofenceCollection}'s name as
74 * `CollectionName` and the deploy-time half grants `actions` on the
75 * collection ARN.
76 */
77export const makeLocationCollectionHttpBinding = <
78 I extends { CollectionName: string },
79 A,
80 E,
81 R,
82>(options: {
83 /** Fully-qualified binding tag, e.g. `AWS.Location.PutGeofence`. */
84 tag: string;
85 /** The distilled operation; `CollectionName` is injected. */
86 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
87 /** IAM actions granted on the geofence collection ARN. */
88 actions: readonly string[];
89}) =>
90 Effect.gen(function* () {
91 const op = yield* options.operation;
92
93 return Effect.fn(function* (collection: GeofenceCollection) {
94 const CollectionName = yield* collection.collectionName;
95 if (!globalThis.__ALCHEMY_RUNTIME__) {
96 const host = yield* Binding.Host;
97 if (isBindingHost(host)) {
98 yield* host.bind`Allow(${host}, ${options.tag}(${collection}))`({
99 policyStatements: [
100 {
101 Effect: "Allow",
102 Action: [...options.actions],
103 Resource: [Output.interpolate`${collection.collectionArn}`],
104 },
105 ],
106 });
107 }
108 }
109 return Effect.fn(`${options.tag}(${collection.LogicalId})`)(function* (
110 request?: Omit<I, "CollectionName">,
111 ) {
112 return yield* op({
113 ...request,
114 CollectionName: yield* CollectionName,
115 } as I);
116 });
117 });
118 });
119
120/**
121 * Build the impl Effect for a place-index-scoped Location operation: the
122 * runtime callable injects the bound {@link PlaceIndex}'s name as `IndexName`
123 * and the deploy-time half grants `actions` on the index ARN.
124 */
125export const makeLocationPlaceIndexHttpBinding = <
126 I extends { IndexName: string },
127 A,
128 E,
129 R,
130>(options: {
131 /** Fully-qualified binding tag, e.g. `AWS.Location.SearchPlaceIndexForText`. */
132 tag: string;
133 /** The distilled operation; `IndexName` is injected. */
134 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
135 /** IAM actions granted on the place index ARN. */
136 actions: readonly string[];
137}) =>
138 Effect.gen(function* () {
139 const op = yield* options.operation;
140
141 return Effect.fn(function* (index: PlaceIndex) {
142 const IndexName = yield* index.indexName;
143 if (!globalThis.__ALCHEMY_RUNTIME__) {
144 const host = yield* Binding.Host;
145 if (isBindingHost(host)) {
146 yield* host.bind`Allow(${host}, ${options.tag}(${index}))`({
147 policyStatements: [
148 {
149 Effect: "Allow",
150 Action: [...options.actions],
151 Resource: [Output.interpolate`${index.indexArn}`],
152 },
153 ],
154 });
155 }
156 }
157 return Effect.fn(`${options.tag}(${index.LogicalId})`)(function* (
158 request?: Omit<I, "IndexName">,
159 ) {
160 return yield* op({
161 ...request,
162 IndexName: yield* IndexName,
163 } as I);
164 });
165 });
166 });
167
168/**
169 * Build the impl Effect for a route-calculator-scoped Location operation: the
170 * runtime callable injects the bound {@link RouteCalculator}'s name as
171 * `CalculatorName` and the deploy-time half grants `actions` on the
172 * calculator ARN.
173 */
174export const makeLocationCalculatorHttpBinding = <
175 I extends { CalculatorName: string },
176 A,
177 E,
178 R,
179>(options: {
180 /** Fully-qualified binding tag, e.g. `AWS.Location.CalculateRoute`. */
181 tag: string;
182 /** The distilled operation; `CalculatorName` is injected. */
183 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
184 /** IAM actions granted on the route calculator ARN. */
185 actions: readonly string[];
186}) =>
187 Effect.gen(function* () {
188 const op = yield* options.operation;
189
190 return Effect.fn(function* (calculator: RouteCalculator) {
191 const CalculatorName = yield* calculator.calculatorName;
192 if (!globalThis.__ALCHEMY_RUNTIME__) {
193 const host = yield* Binding.Host;
194 if (isBindingHost(host)) {
195 yield* host.bind`Allow(${host}, ${options.tag}(${calculator}))`({
196 policyStatements: [
197 {
198 Effect: "Allow",
199 Action: [...options.actions],
200 Resource: [Output.interpolate`${calculator.calculatorArn}`],
201 },
202 ],
203 });
204 }
205 }
206 return Effect.fn(`${options.tag}(${calculator.LogicalId})`)(function* (
207 request?: Omit<I, "CalculatorName">,
208 ) {
209 return yield* op({
210 ...request,
211 CalculatorName: yield* CalculatorName,
212 } as I);
213 });
214 });
215 });
216
217/**
218 * Build the impl Effect for a map-scoped Location operation: the runtime
219 * callable injects the bound {@link LocationMap}'s name as `MapName` and the
220 * deploy-time half grants `actions` on the map ARN.
221 */
222export const makeLocationMapHttpBinding = <
223 I extends { MapName: string },
224 A,
225 E,
226 R,
227>(options: {
228 /** Fully-qualified binding tag, e.g. `AWS.Location.GetMapTile`. */
229 tag: string;
230 /** The distilled operation; `MapName` is injected. */
231 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
232 /** IAM actions granted on the map ARN. */
233 actions: readonly string[];
234}) =>
235 Effect.gen(function* () {
236 const op = yield* options.operation;
237
238 return Effect.fn(function* (map: LocationMap) {
239 const MapName = yield* map.mapName;
240 if (!globalThis.__ALCHEMY_RUNTIME__) {
241 const host = yield* Binding.Host;
242 if (isBindingHost(host)) {
243 yield* host.bind`Allow(${host}, ${options.tag}(${map}))`({
244 policyStatements: [
245 {
246 Effect: "Allow",
247 Action: [...options.actions],
248 Resource: [Output.interpolate`${map.mapArn}`],
249 },
250 ],
251 });
252 }
253 }
254 return Effect.fn(`${options.tag}(${map.LogicalId})`)(function* (
255 request?: Omit<I, "MapName">,
256 ) {
257 return yield* op({
258 ...request,
259 MapName: yield* MapName,
260 } as I);
261 });
262 });
263 });
264
265/**
266 * Build the impl Effect for an account-scoped Location operation (the batch
267 * metadata jobs API: `ListJobs`, `GetJob`, `CancelJob`): jobs are created at
268 * runtime so their ARNs are unknowable at deploy time — the deploy-time half
269 * grants `actions` on `*`.
270 */
271export const makeLocationAccountHttpBinding = <I, A, E, R>(options: {
272 /** Fully-qualified binding tag, e.g. `AWS.Location.ListJobs`. */
273 tag: string;
274 /** The distilled operation, invoked with the caller's request as-is. */
275 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
276 /** IAM actions granted on `*`. */
277 actions: readonly string[];
278}) =>
279 Effect.gen(function* () {
280 const op = yield* options.operation;
281
282 return Effect.fn(function* () {
283 if (!globalThis.__ALCHEMY_RUNTIME__) {
284 const host = yield* Binding.Host;
285 if (isBindingHost(host)) {
286 yield* host.bind`Allow(${host}, ${options.tag}())`({
287 policyStatements: [
288 {
289 Effect: "Allow",
290 Action: [...options.actions],
291 Resource: ["*"],
292 },
293 ],
294 });
295 }
296 }
297 return Effect.fn(options.tag)(function* (request?: I) {
298 return yield* op((request ?? {}) as I);
299 });
300 });
301 });