@reventlessdev/reventless-aws 3.0.0-alpha.265 → 3.0.0-alpha.266

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.266 (2026-08-04)
7
+
8
+ ### Features
9
+
10
+ * **geocoding:** serve geocoding through the platform GraphQL API (D9 half 2) ([914d3c0](https://github.com/ReventlessDev/reventless-core/commit/914d3c0a99a1b67ffb89e2b8259dd30f67ff9bae))
11
+
12
+
6
13
  # 3.0.0-alpha.265 (2026-08-04)
7
14
 
8
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.265",
3
+ "version": "3.0.0-alpha.266",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -13,16 +13,16 @@
13
13
  "uuid": "^13.0.0",
14
14
  "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.3",
15
15
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
16
+ "@reventlessdev/rescript-node": "2.0.0-alpha.1",
16
17
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
17
18
  "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.65",
18
- "@reventlessdev/rescript-node": "2.0.0-alpha.1",
19
19
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
20
20
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
21
- "@reventlessdev/reventless-core": "3.0.0-alpha.210",
22
- "@reventlessdev/reventless-infra": "3.0.0-alpha.125",
23
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.74",
21
+ "@reventlessdev/reventless-core": "3.0.0-alpha.211",
22
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.126",
24
23
  "@reventlessdev/reventless-spec": "3.0.0-alpha.100",
25
- "@reventlessdev/reventless-interop": "3.0.0-alpha.30"
24
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.30",
25
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.75"
26
26
  },
27
27
  "devDependencies": {
28
28
  "rescript": "12.3.0",
package/src/Platform.res CHANGED
@@ -82,24 +82,21 @@ let objectStoreEndpointsRef: ref<array<objectStoreEndpoint>> = ref([])
82
82
  `getApiConfig` does. */
83
83
  let getObjectStoreEndpoints = () => objectStoreEndpointsRef.contents
84
84
 
85
- /** The geocoder endpoint this platform provisioned, for the monolithic case.
85
+ /** The geocoding place index this platform provisioned, for the monolithic case.
86
86
 
87
- In plugin mode the value crosses to the plugin stack as the `geocoderEndpoint`
87
+ In plugin mode the value crosses to the plugin stack as the `geocoderPlaceIndex`
88
88
  export; when platform and plugin are one program there is no stack to read, so
89
89
  `deployPlatform` records it here and `deployPlugin` picks it up — the same two
90
- halves `objectStoreEndpointsRef` exists for.
90
+ halves `objectStoreEndpointsRef` exists for. Slice Lambdas call Amazon Location
91
+ directly with this index name; the browser reaches the same capability through
92
+ the platform API's `Query.geocode` resolver (D9 half 2), so there is no endpoint
93
+ to carry beside it.
91
94
 
92
95
  A plain Output with `""` as the "no geocoder" sentinel, never
93
96
  `option<Pulumi.Output.t<_>>`: any generic `Option.*` over an Output runs
94
97
  `valFromOption`, whose nested-option probe hits the Output proxy — where every
95
98
  property access returns a truthy Output — and corrupts it. Same reasoning, and
96
99
  the same sentinel, as `PluginRuntime_Builder.inboundSliceReg.auditTableName`. */
97
- let geocoderEndpointRef: ref<Pulumi.Output.t<string>> = ref(Pulumi.Output.make(""))
98
-
99
- /** The geocoding place index, for the monolithic case — the SDK-side half of the
100
- same capability, carried for exactly the reasons above and with the same `""`
101
- sentinel. Slice Lambdas call Amazon Location directly with this; only the
102
- browser needs the Function URL. */
103
100
  let geocoderPlaceIndexRef: ref<Pulumi.Output.t<string>> = ref(Pulumi.Output.make(""))
104
101
 
105
102
  module MakeWithConfig = (
@@ -248,9 +245,15 @@ module MakeWithConfig = (
248
245
  // unified mode carries the admin base here instead; it declares no stores in practice,
249
246
  // so the upload resolvers below are never created there.)
250
247
  let domainBaseFragment = ReventlessCore.GraphQL_Stitcher.encode({
251
- types: ReventlessCore.Platform_AdminApi.uploadTypes,
248
+ types: Array.concat(
249
+ ReventlessCore.Platform_AdminApi.uploadTypes,
250
+ ReventlessCore.Platform_AdminApi.geocodeTypes,
251
+ ),
252
252
  mutations: ReventlessCore.Platform_AdminApi.uploadMutationFields,
253
- queries: [" Platform_ping: String"],
253
+ queries: Array.concat(
254
+ [" Platform_ping: String"],
255
+ ReventlessCore.Platform_AdminApi.geocodeQueryFields,
256
+ ),
254
257
  subscriptions: [],
255
258
  subscriptionSources: [],
256
259
  })
@@ -1152,10 +1155,10 @@ module MakeWithConfig = (
1152
1155
  // unchanged. Single-mode shape: one origin-relative file per deployment.
1153
1156
  uiHintsFile?: string,
1154
1157
  // Optional geocoding place index, as returned by
1155
- // `Capability_Geocoding_AwsLocation.make`. When set, the deploy provisions a
1156
- // public geocoder Lambda Function URL (Geocoder_AwsLocation) and threads its
1157
- // URL into config.json as `geocoderEndpoint`. Unset no service, field
1158
- // omitted (byte-identical config.json).
1158
+ // `Capability_Geocoding_AwsLocation.make`. When set, the deploy provisions the
1159
+ // capability's client door a Cognito-authenticated `Query.geocode` resolver
1160
+ // on the domain API (`Geocoder_AwsLocation_Resolver`, split mode) and exports
1161
+ // the index name for the unattended slice path. Unset ⇒ no resolver, no export.
1159
1162
  geocoderPlaceIndex?: ReventlessInfra.Platform.geocoderIndex,
1160
1163
  // Optional object store for direct-to-S3 uploads, as returned by
1161
1164
  // `Capability_ObjectStore_S3.make`. When set, the deploy registers it with
@@ -2066,35 +2069,32 @@ module MakeWithConfig = (
2066
2069
  | None => Pulumi.Output.make(None)
2067
2070
  }
2068
2071
 
2069
- // Optional public geocoder Function URL. Provisioned only when a place
2070
- // index is configured; its resolved URL becomes config.json's
2071
- // `geocoderEndpoint`. Unset Output<None> field omitted below.
2072
- let geocoderEndpointOutput: Pulumi.Output.t<option<string>> = switch cfg.geocoderPlaceIndex {
2073
- | Some(index) =>
2074
- Geocoder_AwsLocation.make(
2072
+ // The geocoding capability's client door (D9 half 2): a `Query.geocode`
2073
+ // resolver on the domain API, provisioned only when a place index is
2074
+ // configured. Authenticated by the API's Cognito authorizer, so it replaces
2075
+ // the public unauthenticated Function URL the browser used to call — after
2076
+ // which that endpoint has no callers at all (the unattended slice path reaches
2077
+ // the same index through the SDK, via `geocoderPlaceIndex` below).
2078
+ //
2079
+ // Split-API only, for the same reason the upload resolvers are: `geocode`
2080
+ // lives on the domain base document (`domainBaseFragment`), which a
2081
+ // unified-mode deployment's single API does not carry.
2082
+ switch cfg.geocoderPlaceIndex {
2083
+ | Some(index) if Config.splitApi =>
2084
+ let _geocodeService = Geocoder_AwsLocation_Resolver.make(
2085
+ ~api=domainApi,
2075
2086
  ~placeIndexName=index.indexName,
2076
- ).url->Pulumi.Output.apply(u => Some(u))
2077
- | None => Pulumi.Output.make(None)
2087
+ ~opts={},
2088
+ )
2089
+ | _ => ()
2078
2090
  }
2079
2091
 
2080
- // Exported as well as written into config.json, because the browser is no
2081
- // longer the only caller: a plugin stack's slice Lambdas geocode too, and a
2082
- // plugin stack has no other way to see a value computed in this one.
2083
- //
2084
- // `""` when unset rather than a missing output, so the read side has one
2085
- // shape to handle instead of two — and "" is already what a client reads as
2086
- // "no geocoder configured".
2087
- let geocoderEndpointFlat =
2088
- geocoderEndpointOutput->Pulumi.Output.apply(o => o->Option.getOr(""))
2089
- Pulumi.Pulumi.export("geocoderEndpoint", geocoderEndpointFlat)
2090
- geocoderEndpointRef := geocoderEndpointFlat
2091
-
2092
- // The place index itself, exported beside the Function URL because the two
2093
- // callers need different things from the same capability. A browser cannot
2094
- // sign an SDK call and gets the URL; a slice's Lambda can, and gets the
2095
- // index name — no proxy hop, and no dependence on a public unauthenticated
2096
- // endpoint for an unattended path. `""` when unset, for the same
2097
- // one-shape-to-handle reason as the endpoint above.
2092
+ // The place index itself, exported so the unattended slice path can reach the
2093
+ // capability: a slice's Lambda signs an SDK call directly and needs the index
2094
+ // name, not an endpoint (no proxy hop, no dependence on a public URL). The
2095
+ // browser reaches the same capability through the `Query.geocode` resolver
2096
+ // above. `""` when unset, so a plugin stack reading this output has one shape
2097
+ // to handle rather than two.
2098
2098
  let geocoderPlaceIndexFlat = switch cfg.geocoderPlaceIndex {
2099
2099
  | Some(index) => index.indexName->Pulumi.Output.fromInput
2100
2100
  | None => Pulumi.Output.make("")
@@ -2116,14 +2116,9 @@ module MakeWithConfig = (
2116
2116
  cognitoPool.clientId,
2117
2117
  )->Pulumi.Output.all4,
2118
2118
  domainEventsEndpointOutput,
2119
- geocoderEndpointOutput,
2120
2119
  )
2121
- ->Pulumi.Output.all3
2122
- ->Pulumi.Output.apply(((
2123
- (domainEp, platformEp, poolId, clientId),
2124
- eventsEpOpt,
2125
- geocoderEpOpt,
2126
- )) => {
2120
+ ->Pulumi.Output.all2
2121
+ ->Pulumi.Output.apply((((domainEp, platformEp, poolId, clientId), eventsEpOpt)) => {
2127
2122
  let computed = [
2128
2123
  ("apiEndpoint", JSON.Encode.string(domainEp)),
2129
2124
  ("platformApiEndpoint", JSON.Encode.string(platformEp)),
@@ -2151,12 +2146,12 @@ module MakeWithConfig = (
2151
2146
  )
2152
2147
  | None => computed
2153
2148
  }
2154
- let withGeocoder = switch geocoderEpOpt {
2155
- | Some(ep) => Array.concat(withEvents, [("geocoderEndpoint", JSON.Encode.string(ep))])
2156
- | None => withEvents
2157
- }
2149
+ // `geocoderEndpoint` is no longer written: the browser reaches geocoding
2150
+ // through the platform API's `Query.geocode` resolver (D9 half 2), the
2151
+ // same way uploads moved off a per-store URL onto `Upload_Presign`. The
2152
+ // public Function URL and its config key are both gone.
2158
2153
  Util_ShellConfig.fields(
2159
- ~computed=withGeocoder,
2154
+ ~computed=withEvents,
2160
2155
  ~viewModes=?cfg.viewModes,
2161
2156
  ~shellConfig=?cfg.shellConfig,
2162
2157
  )
@@ -59,7 +59,6 @@ import * as AppSync_SdlDecorate$ReventlessAws from "./components/Api/AppSync_Sdl
59
59
  import * as UiFragmentRegistry$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs";
60
60
  import * as Util_ResourceNaming$ReventlessAws from "./util/Util_ResourceNaming.res.mjs";
61
61
  import * as ClonerRunner_Fargate$ReventlessAws from "./plugin/cloner/ClonerRunner_Fargate.res.mjs";
62
- import * as Geocoder_AwsLocation$ReventlessAws from "./adapter/Geocoder/Geocoder_AwsLocation.res.mjs";
63
62
  import * as QueryEngine_DynamoDb$ReventlessAws from "./adapter/QueryEngine/QueryEngine_DynamoDb.res.mjs";
64
63
  import * as PluginRuntime_Builder$ReventlessAws from "./plugin/runtime/PluginRuntime_Builder.res.mjs";
65
64
  import * as PluginsReadModelSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs";
@@ -85,6 +84,7 @@ import * as CommandTopicChannel_SQS_Sync$ReventlessAws from "./adapter/CommandTo
85
84
  import * as EventLogSubscription_AppSync$ReventlessAws from "./adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs";
86
85
  import * as UiFragmentRegistry_Behavior$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry_Behavior.res.mjs";
87
86
  import * as CommandTopicChannel_SQS_Async$ReventlessAws from "./adapter/CommandTopic/CommandTopicChannel_SQS_Async.res.mjs";
87
+ import * as Geocoder_AwsLocation_Resolver$ReventlessAws from "./adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs";
88
88
  import * as Plugin_ExtensionPoint_Builder$ReventlessAws from "./plugin/stack/Plugin_ExtensionPoint_Builder.res.mjs";
89
89
  import * as QueryDbStorage_DynamoDbStream$ReventlessAws from "./adapter/QueryDb/QueryDbStorage_DynamoDbStream.res.mjs";
90
90
  import * as StateViewSlice_Builder_Stream$ReventlessAws from "./components/StateViewSlice_Builder_Stream.res.mjs";
@@ -130,10 +130,6 @@ function getObjectStoreEndpoints() {
130
130
  return objectStoreEndpointsRef.contents;
131
131
  }
132
132
 
133
- let geocoderEndpointRef = {
134
- contents: Pulumi.output("")
135
- };
136
-
137
133
  let geocoderPlaceIndexRef = {
138
134
  contents: Pulumi.output("")
139
135
  };
@@ -193,9 +189,9 @@ function MakeWithConfig(Config) {
193
189
  }
194
190
  };
195
191
  let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
196
- types: Platform_AdminApi$ReventlessCore.uploadTypes,
192
+ types: Platform_AdminApi$ReventlessCore.uploadTypes.concat(Platform_AdminApi$ReventlessCore.geocodeTypes),
197
193
  mutations: Platform_AdminApi$ReventlessCore.uploadMutationFields,
198
- queries: [" Platform_ping: String"],
194
+ queries: [" Platform_ping: String"].concat(Platform_AdminApi$ReventlessCore.geocodeQueryFields),
199
195
  subscriptions: [],
200
196
  subscriptionSources: []
201
197
  });
@@ -1131,10 +1127,9 @@ function MakeWithConfig(Config) {
1131
1127
  let cognitoPool = Platform_Stack$ReventlessAws.resolveCognitoUserPool();
1132
1128
  let domainEventsEndpointOutput = domainEventsApiOpt !== undefined ? AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt).apply(ep => ep + "/event") : Pulumi.output(undefined);
1133
1129
  let index = hostUiBundle.geocoderPlaceIndex;
1134
- let geocoderEndpointOutput = index !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(index.indexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
1135
- let geocoderEndpointFlat = geocoderEndpointOutput.apply(o => Stdlib_Option.getOr(o, ""));
1136
- Pulumi$Pulumi.$$export("geocoderEndpoint", geocoderEndpointFlat);
1137
- geocoderEndpointRef.contents = geocoderEndpointFlat;
1130
+ if (index !== undefined && Config.splitApi) {
1131
+ Geocoder_AwsLocation_Resolver$ReventlessAws.make(domainApi, index.indexName, undefined, {});
1132
+ }
1138
1133
  let index$1 = hostUiBundle.geocoderPlaceIndex;
1139
1134
  let geocoderPlaceIndexFlat = index$1 !== undefined ? index$1.indexName : Pulumi.output("");
1140
1135
  Pulumi$Pulumi.$$export("geocoderPlaceIndex", geocoderPlaceIndexFlat);
@@ -1146,10 +1141,8 @@ function MakeWithConfig(Config) {
1146
1141
  cognitoPool.poolId,
1147
1142
  cognitoPool.clientId
1148
1143
  ]),
1149
- domainEventsEndpointOutput,
1150
- geocoderEndpointOutput
1144
+ domainEventsEndpointOutput
1151
1145
  ]).apply(param => {
1152
- let geocoderEpOpt = param[2];
1153
1146
  let eventsEpOpt = param[1];
1154
1147
  let match = param[0];
1155
1148
  let computed = [
@@ -1196,11 +1189,7 @@ function MakeWithConfig(Config) {
1196
1189
  AppSync_EventsApi$ReventlessAws.clientNamespaceName
1197
1190
  ]
1198
1191
  ]) : computed;
1199
- let withGeocoder = geocoderEpOpt !== undefined ? withEvents.concat([[
1200
- "geocoderEndpoint",
1201
- geocoderEpOpt
1202
- ]]) : withEvents;
1203
- return JSON.stringify(Util_ShellConfig$ReventlessAws.fields(withGeocoder, hostUiBundle.viewModes, hostUiBundle.shellConfig));
1192
+ return JSON.stringify(Util_ShellConfig$ReventlessAws.fields(withEvents, hostUiBundle.viewModes, hostUiBundle.shellConfig));
1204
1193
  });
1205
1194
  new (Aws.s3.BucketObject)("host-ui-config-json", {
1206
1195
  bucket: bucketName,
@@ -1452,9 +1441,9 @@ function Make($star) {
1452
1441
  }
1453
1442
  };
1454
1443
  let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
1455
- types: Platform_AdminApi$ReventlessCore.uploadTypes,
1444
+ types: Platform_AdminApi$ReventlessCore.uploadTypes.concat(Platform_AdminApi$ReventlessCore.geocodeTypes),
1456
1445
  mutations: Platform_AdminApi$ReventlessCore.uploadMutationFields,
1457
- queries: [" Platform_ping: String"],
1446
+ queries: [" Platform_ping: String"].concat(Platform_AdminApi$ReventlessCore.geocodeQueryFields),
1458
1447
  subscriptions: [],
1459
1448
  subscriptionSources: []
1460
1449
  });
@@ -2367,10 +2356,9 @@ function Make($star) {
2367
2356
  let cognitoPool = Platform_Stack$ReventlessAws.resolveCognitoUserPool();
2368
2357
  let domainEventsEndpointOutput = domainEventsApiOpt !== undefined ? AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt).apply(ep => ep + "/event") : Pulumi.output(undefined);
2369
2358
  let index = hostUiBundle.geocoderPlaceIndex;
2370
- let geocoderEndpointOutput = index !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(index.indexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
2371
- let geocoderEndpointFlat = geocoderEndpointOutput.apply(o => Stdlib_Option.getOr(o, ""));
2372
- Pulumi$Pulumi.$$export("geocoderEndpoint", geocoderEndpointFlat);
2373
- geocoderEndpointRef.contents = geocoderEndpointFlat;
2359
+ if (index !== undefined) {
2360
+ Geocoder_AwsLocation_Resolver$ReventlessAws.make(domainApi, index.indexName, undefined, {});
2361
+ }
2374
2362
  let index$1 = hostUiBundle.geocoderPlaceIndex;
2375
2363
  let geocoderPlaceIndexFlat = index$1 !== undefined ? index$1.indexName : Pulumi.output("");
2376
2364
  Pulumi$Pulumi.$$export("geocoderPlaceIndex", geocoderPlaceIndexFlat);
@@ -2382,10 +2370,8 @@ function Make($star) {
2382
2370
  cognitoPool.poolId,
2383
2371
  cognitoPool.clientId
2384
2372
  ]),
2385
- domainEventsEndpointOutput,
2386
- geocoderEndpointOutput
2373
+ domainEventsEndpointOutput
2387
2374
  ]).apply(param => {
2388
- let geocoderEpOpt = param[2];
2389
2375
  let eventsEpOpt = param[1];
2390
2376
  let match = param[0];
2391
2377
  let computed = [
@@ -2432,11 +2418,7 @@ function Make($star) {
2432
2418
  AppSync_EventsApi$ReventlessAws.clientNamespaceName
2433
2419
  ]
2434
2420
  ]) : computed;
2435
- let withGeocoder = geocoderEpOpt !== undefined ? withEvents.concat([[
2436
- "geocoderEndpoint",
2437
- geocoderEpOpt
2438
- ]]) : withEvents;
2439
- return JSON.stringify(Util_ShellConfig$ReventlessAws.fields(withGeocoder, hostUiBundle.viewModes, hostUiBundle.shellConfig));
2421
+ return JSON.stringify(Util_ShellConfig$ReventlessAws.fields(withEvents, hostUiBundle.viewModes, hostUiBundle.shellConfig));
2440
2422
  });
2441
2423
  new (Aws.s3.BucketObject)("host-ui-config-json", {
2442
2424
  bucket: bucketName,
@@ -2645,7 +2627,6 @@ export {
2645
2627
  getSplitApiOutputs,
2646
2628
  objectStoreEndpointsRef,
2647
2629
  getObjectStoreEndpoints,
2648
- geocoderEndpointRef,
2649
2630
  geocoderPlaceIndexRef,
2650
2631
  MakeWithConfig,
2651
2632
  Make,
@@ -0,0 +1,227 @@
1
+ // AWS Location Service geocoder, mounted on the platform GraphQL API.
2
+ //
3
+ // Deploy-time only: `make` provisions one compiled-EntryPoint Lambda (a plain
4
+ // `Lambda.Function` whose code archive re-exports `handler` from the compiled,
5
+ // type-checked runtime module `Geocoder_AwsLocation_Resolver_Ops` and ships the
6
+ // shared ESM resolve-hook loader), an IAM execution role scoped to CloudWatch Logs
7
+ // + `geo:SearchPlaceIndexForText` on the target place index, an AppSync Lambda data
8
+ // source, and one resolver — `Query.geocode` — on the platform API.
9
+ //
10
+ // There is no Function URL and no anonymous surface: authentication is the platform
11
+ // API's Cognito authorizer. This is the *client* door of the geocoding capability
12
+ // (D9 half 2), replacing the public Function URL the browser used to call. The
13
+ // unattended slice path reaches the same place index through the SDK directly
14
+ // (`Geocoder_AwsLocation_Backend`), so one capability, two doors — the same shape
15
+ // the object store already has with `Upload_Presign` and `Offload.resolve`.
16
+ //
17
+ // Why an EntryPoint and not a Pulumi `CallbackFunction`: same SDK-skew reason as the
18
+ // upload service — a serialized closure bakes the deploy machine's version-specific
19
+ // AWS SDK internals into the archive but then resolves `@smithy/*`/`@aws-sdk/*`
20
+ // transitives from independently-versioned layer/runtime sources that disagree at
21
+ // cold start. Shipping the compiled `_Ops` module with bare `@aws-sdk/*` imports,
22
+ // resolved through the resolve-hook, loads one internally consistent SDK. The
23
+ // runtime logic lives in [Geocoder_AwsLocation_Resolver_Ops.res].
24
+
25
+ open PulumiAws
26
+
27
+ type serviceOutputs = {resources: array<Pulumi.Output.t<string>>}
28
+
29
+ // JS resolver code (APPSYNC_JS runtime): forward the caller's arguments to the
30
+ // Lambda. CORS and auth belong to the API, not to this code. No identity is
31
+ // forwarded — geocoding is not scoped per caller (any authenticated user may
32
+ // resolve any address), unlike the upload service which namespaces objects by the
33
+ // verified `sub`.
34
+ let invokeCode: Pulumi.Input.t<string> =
35
+ `import { util } from '@aws-appsync/utils';
36
+ export function request(ctx) {
37
+ return { operation: 'Invoke', payload: { arguments: ctx.args } };
38
+ }
39
+ export function response(ctx) {
40
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
41
+ return ctx.result;
42
+ }
43
+ `->Pulumi.Input.make
44
+
45
+ let make = (
46
+ ~api: Pulumi.Output.t<AppSync.GraphQLApi.t>,
47
+ ~placeIndexName: Pulumi.Input.t<string>,
48
+ ~name: string="GeocodeService",
49
+ ~opts: Pulumi.ComponentResource.options,
50
+ ): serviceOutputs => {
51
+ let opts = opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
52
+
53
+ let lambdaRole = IAM.Role.makeWithDefaultPolicy(
54
+ ~name=name ++ "Lambda",
55
+ ~servicePrincipal=AWS.Lambda.principal->Pulumi.Output.make,
56
+ ~tags=AWS.Tags.make(
57
+ ~name=name ++ "Lambda",
58
+ ~kind=ReventlessCore.ComponentType.Platform,
59
+ ~role=Identity,
60
+ ~scope=Platform,
61
+ ),
62
+ ~opts,
63
+ )
64
+
65
+ // CloudWatch Logs (so failures are observable) plus least-privilege
66
+ // `geo:SearchPlaceIndexForText` on the one place index — the same grant the
67
+ // Function URL flavour carried, now on a resolver-invoked Lambda.
68
+ let _policy =
69
+ placeIndexName
70
+ ->Pulumi.Output.fromInput
71
+ ->Pulumi.Output.apply(idx => {
72
+ let arn = `arn:aws:geo:*:*:place-index/${idx}`
73
+ let _ = IAM.RolePolicy.make(
74
+ ~name=name ++ "LambdaPolicy",
75
+ ~args={
76
+ policy: PolicyDocument.make(
77
+ ~id=name ++ "LambdaPolicy",
78
+ ~statements=[
79
+ {
80
+ sid: "AllowLambdaLogging",
81
+ effect: Allow,
82
+ actions: Actions(["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"]),
83
+ resources: Resource("arn:aws:logs:*:*:*"),
84
+ },
85
+ {
86
+ sid: "AllowGeocode",
87
+ effect: Allow,
88
+ actions: Action("geo:SearchPlaceIndexForText"),
89
+ resources: Resource(arn),
90
+ },
91
+ ],
92
+ )
93
+ ->PolicyDocument.toJsonString
94
+ ->Pulumi.Input.make,
95
+ role: lambdaRole.id->Pulumi.Output.asInput,
96
+ },
97
+ ~opts,
98
+ )
99
+ })
100
+
101
+ // Bundle reventless-aws (the compiled `_Ops` handler lives inside it) and
102
+ // re-export its `handler`; buildCodeArchive also ships the ESM resolve-hook.
103
+ let packageDirs = Dict.fromArray([
104
+ (
105
+ "@reventlessdev/reventless-aws",
106
+ Util_Bundle.resolvePackageRoot("@reventlessdev/reventless-aws"),
107
+ ),
108
+ ])
109
+ let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
110
+ ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver_Ops.res.mjs",
111
+ ~packageDirs,
112
+ )
113
+
114
+ let layers =
115
+ Lambda.reventlessLayerArn
116
+ ->Option.map(arn => [arn->Pulumi.Input.make])
117
+ ->Option.getOr([])
118
+ ->Pulumi.Input.make
119
+
120
+ let lambda = Lambda.Function.make(
121
+ ~name=name ++ "Lambda",
122
+ ~args={
123
+ handler: "index.handler"->Pulumi.Input.make,
124
+ runtime: "nodejs22.x"->Pulumi.Input.make,
125
+ code: code->Pulumi.Input.make,
126
+ sourceCodeHash: sourceCodeHash->Pulumi.Input.make,
127
+ role: lambdaRole.arn->Pulumi.Output.asInput,
128
+ memorySize: 256->Pulumi.Input.make,
129
+ timeout: 30->Pulumi.Input.make,
130
+ layers,
131
+ tags: AWS.Tags.make(
132
+ ~name=name ++ "Lambda",
133
+ ~kind=ReventlessCore.ComponentType.Platform,
134
+ ~role=Runtime,
135
+ ~scope=Platform,
136
+ ),
137
+ environment: (
138
+ {
139
+ Lambda.Function.variables: Dict.fromArray([
140
+ ("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
141
+ ("PLACE_INDEX_NAME", placeIndexName),
142
+ ("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
143
+ ("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
144
+ Util_LambdaLogging.logLevelEntry(),
145
+ ]),
146
+ }: Lambda.Function.functionEnvironment
147
+ )->Pulumi.Input.make,
148
+ },
149
+ ~opts,
150
+ )
151
+
152
+ Util_LambdaLogging.makeManagedLogGroup(
153
+ ~name=name ++ "Lambda",
154
+ ~lambdaName=lambda.name,
155
+ ~tags=AWS.Tags.make(
156
+ ~name=name ++ "LambdaLogGroup",
157
+ ~kind=ReventlessCore.ComponentType.Platform,
158
+ ~role=Logs,
159
+ ~scope=Platform,
160
+ ),
161
+ ~opts,
162
+ (),
163
+ )
164
+
165
+ let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
166
+ ~name=name ++ "DataSource",
167
+ ~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
168
+ ~tags=AWS.Tags.make(
169
+ ~name=name ++ "DataSource",
170
+ ~kind=ReventlessCore.ComponentType.Platform,
171
+ ~role=Identity,
172
+ ~scope=Platform,
173
+ ),
174
+ ~opts,
175
+ )
176
+
177
+ let _ =
178
+ (lambda.arn, dataSourceRole.id)
179
+ ->Pulumi.Output.all2
180
+ ->Pulumi.Output.apply(((lambdaArn, dataSourceRoleId)) => {
181
+ let _attach = IAM.RolePolicy.make(
182
+ ~name=name ++ "DataSource",
183
+ ~args={
184
+ policy: PolicyDocument.make(
185
+ ~id=name ++ "DataSourcePolicy",
186
+ ~statements=[
187
+ {
188
+ sid: "AllowDataSourceInvokeLambda",
189
+ effect: Allow,
190
+ actions: Action("lambda:InvokeFunction"),
191
+ resources: Resource(lambdaArn),
192
+ },
193
+ ],
194
+ )
195
+ ->PolicyDocument.toJsonString
196
+ ->Pulumi.Input.make,
197
+ role: dataSourceRoleId->Pulumi.Input.make,
198
+ },
199
+ ~opts,
200
+ )
201
+ })
202
+
203
+ let dataSource = AppSync.DataSource.make(
204
+ ~name=name ++ "DataSource",
205
+ ~args={
206
+ type_: AWS_LAMBDA,
207
+ apiId: api->Pulumi.Output.flatMap(api => api.id)->Pulumi.Output.asInput,
208
+ lambdaConfig: {
209
+ AppSync.DataSource.functionArn: lambda.arn->Pulumi.Output.asInput,
210
+ }->Pulumi.Input.make,
211
+ serviceRoleArn: dataSourceRole.arn->Pulumi.Output.asInput,
212
+ },
213
+ ~opts=Some(opts),
214
+ )
215
+
216
+ let _geocodeResolver = AppSync_Resolver_Native.makeUnitJsResolver(
217
+ ~name=name ++ "Geocode",
218
+ ~api,
219
+ ~dataSourceName=dataSource.name->Pulumi.Output.asInput,
220
+ ~type_="Query"->Pulumi.Input.make,
221
+ ~field="geocode"->Pulumi.Input.make,
222
+ ~code=invokeCode,
223
+ ~opts,
224
+ )
225
+
226
+ {resources: [lambda.arn]}
227
+ }