@reventlessdev/reventless-aws 3.0.0-alpha.264 → 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.
Files changed (27) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +7 -7
  3. package/src/Platform.res +66 -45
  4. package/src/Platform.res.mjs +31 -35
  5. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +227 -0
  6. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +127 -0
  7. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver_Ops.res +55 -0
  8. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver_Ops.res.mjs +48 -0
  9. package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +21 -3
  10. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +219 -27
  11. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +121 -7
  12. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +118 -0
  13. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +61 -0
  14. package/src/components/OutboundTranslationSlice_Builder.res +15 -0
  15. package/src/components/OutboundTranslationSlice_Builder.res.mjs +11 -1
  16. package/src/plugin/runtime/PluginRuntime_Builder.res +26 -0
  17. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +26 -0
  18. package/tests/AutomationSliceEntryPoint_OpsTest.res +44 -3
  19. package/tests/AutomationSliceEntryPoint_OpsTest.res.mjs +65 -1
  20. package/tests/Geocoder_AwsLocation_Resolver_OpsTest.res +53 -0
  21. package/tests/Geocoder_AwsLocation_Resolver_OpsTest.res.mjs +55 -0
  22. package/src/adapter/Geocoder/Geocoder_AwsLocation.res +0 -166
  23. package/src/adapter/Geocoder/Geocoder_AwsLocation.res.mjs +0 -105
  24. package/src/adapter/Geocoder/Geocoder_AwsLocation_Ops.res +0 -144
  25. package/src/adapter/Geocoder/Geocoder_AwsLocation_Ops.res.mjs +0 -137
  26. package/tests/Geocoder_AwsLocation_OpsTest.res +0 -78
  27. package/tests/Geocoder_AwsLocation_OpsTest.res.mjs +0 -71
package/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
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
+
13
+ # 3.0.0-alpha.265 (2026-08-04)
14
+
15
+ ### Bug Fixes
16
+
17
+ * **aws:** make a slice's TODO backlog survive, and actually sweep it ([f35dcbd](https://github.com/ReventlessDev/reventless-core/commit/f35dcbd86374124106c2d1e48d29c6f41fdbec2c))
18
+ ### Features
19
+
20
+ * **outbound:** hand translate its geocoder instead of making it fetch one ([fb18312](https://github.com/ReventlessDev/reventless-core/commit/fb1831216b37c9562868c46e1a09054e69418c67))
21
+
22
+
6
23
  # 3.0.0-alpha.264 (2026-08-03)
7
24
 
8
25
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.264",
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.209",
22
- "@reventlessdev/reventless-infra": "3.0.0-alpha.124",
23
- "@reventlessdev/reventless-spec": "3.0.0-alpha.99",
24
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.73",
25
- "@reventlessdev/reventless-interop": "3.0.0-alpha.30"
21
+ "@reventlessdev/reventless-core": "3.0.0-alpha.211",
22
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.126",
23
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.100",
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,19 +82,22 @@ 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(""))
100
+ let geocoderPlaceIndexRef: ref<Pulumi.Output.t<string>> = ref(Pulumi.Output.make(""))
98
101
 
99
102
  module MakeWithConfig = (
100
103
  Config: {
@@ -242,9 +245,15 @@ module MakeWithConfig = (
242
245
  // unified mode carries the admin base here instead; it declares no stores in practice,
243
246
  // so the upload resolvers below are never created there.)
244
247
  let domainBaseFragment = ReventlessCore.GraphQL_Stitcher.encode({
245
- types: ReventlessCore.Platform_AdminApi.uploadTypes,
248
+ types: Array.concat(
249
+ ReventlessCore.Platform_AdminApi.uploadTypes,
250
+ ReventlessCore.Platform_AdminApi.geocodeTypes,
251
+ ),
246
252
  mutations: ReventlessCore.Platform_AdminApi.uploadMutationFields,
247
- queries: [" Platform_ping: String"],
253
+ queries: Array.concat(
254
+ [" Platform_ping: String"],
255
+ ReventlessCore.Platform_AdminApi.geocodeQueryFields,
256
+ ),
248
257
  subscriptions: [],
249
258
  subscriptionSources: [],
250
259
  })
@@ -1146,10 +1155,10 @@ module MakeWithConfig = (
1146
1155
  // unchanged. Single-mode shape: one origin-relative file per deployment.
1147
1156
  uiHintsFile?: string,
1148
1157
  // Optional geocoding place index, as returned by
1149
- // `Capability_Geocoding_AwsLocation.make`. When set, the deploy provisions a
1150
- // public geocoder Lambda Function URL (Geocoder_AwsLocation) and threads its
1151
- // URL into config.json as `geocoderEndpoint`. Unset no service, field
1152
- // 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.
1153
1162
  geocoderPlaceIndex?: ReventlessInfra.Platform.geocoderIndex,
1154
1163
  // Optional object store for direct-to-S3 uploads, as returned by
1155
1164
  // `Capability_ObjectStore_S3.make`. When set, the deploy registers it with
@@ -2060,28 +2069,38 @@ module MakeWithConfig = (
2060
2069
  | None => Pulumi.Output.make(None)
2061
2070
  }
2062
2071
 
2063
- // Optional public geocoder Function URL. Provisioned only when a place
2064
- // index is configured; its resolved URL becomes config.json's
2065
- // `geocoderEndpoint`. Unset Output<None> field omitted below.
2066
- let geocoderEndpointOutput: Pulumi.Output.t<option<string>> = switch cfg.geocoderPlaceIndex {
2067
- | Some(index) =>
2068
- 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,
2069
2086
  ~placeIndexName=index.indexName,
2070
- ).url->Pulumi.Output.apply(u => Some(u))
2071
- | None => Pulumi.Output.make(None)
2087
+ ~opts={},
2088
+ )
2089
+ | _ => ()
2072
2090
  }
2073
2091
 
2074
- // Exported as well as written into config.json, because the browser is no
2075
- // longer the only caller: a plugin stack's slice Lambdas geocode too, and a
2076
- // plugin stack has no other way to see a value computed in this one.
2077
- //
2078
- // `""` when unset rather than a missing output, so the read side has one
2079
- // shape to handle instead of two — and "" is already what a client reads as
2080
- // "no geocoder configured".
2081
- let geocoderEndpointFlat =
2082
- geocoderEndpointOutput->Pulumi.Output.apply(o => o->Option.getOr(""))
2083
- Pulumi.Pulumi.export("geocoderEndpoint", geocoderEndpointFlat)
2084
- geocoderEndpointRef := geocoderEndpointFlat
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
+ let geocoderPlaceIndexFlat = switch cfg.geocoderPlaceIndex {
2099
+ | Some(index) => index.indexName->Pulumi.Output.fromInput
2100
+ | None => Pulumi.Output.make("")
2101
+ }
2102
+ Pulumi.Pulumi.export("geocoderPlaceIndex", geocoderPlaceIndexFlat)
2103
+ geocoderPlaceIndexRef := geocoderPlaceIndexFlat
2085
2104
 
2086
2105
  // Presign endpoints are no longer written to config.json: under route B the
2087
2106
  // client calls the platform API's `Upload_Presign` mutation (reachable from
@@ -2097,14 +2116,9 @@ module MakeWithConfig = (
2097
2116
  cognitoPool.clientId,
2098
2117
  )->Pulumi.Output.all4,
2099
2118
  domainEventsEndpointOutput,
2100
- geocoderEndpointOutput,
2101
2119
  )
2102
- ->Pulumi.Output.all3
2103
- ->Pulumi.Output.apply(((
2104
- (domainEp, platformEp, poolId, clientId),
2105
- eventsEpOpt,
2106
- geocoderEpOpt,
2107
- )) => {
2120
+ ->Pulumi.Output.all2
2121
+ ->Pulumi.Output.apply((((domainEp, platformEp, poolId, clientId), eventsEpOpt)) => {
2108
2122
  let computed = [
2109
2123
  ("apiEndpoint", JSON.Encode.string(domainEp)),
2110
2124
  ("platformApiEndpoint", JSON.Encode.string(platformEp)),
@@ -2132,12 +2146,12 @@ module MakeWithConfig = (
2132
2146
  )
2133
2147
  | None => computed
2134
2148
  }
2135
- let withGeocoder = switch geocoderEpOpt {
2136
- | Some(ep) => Array.concat(withEvents, [("geocoderEndpoint", JSON.Encode.string(ep))])
2137
- | None => withEvents
2138
- }
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.
2139
2153
  Util_ShellConfig.fields(
2140
- ~computed=withGeocoder,
2154
+ ~computed=withEvents,
2141
2155
  ~viewModes=?cfg.viewModes,
2142
2156
  ~shellConfig=?cfg.shellConfig,
2143
2157
  )
@@ -2251,15 +2265,22 @@ module MakeWithConfig = (
2251
2265
  // is a modelled outcome, which is the point of the empty string: a plugin
2252
2266
  // deployed against a platform without the capability degrades rather than
2253
2267
  // failing to deploy.
2254
- let geocoderEndpoint: Pulumi.Output.t<string> = switch platformStackRef {
2268
+ // The place index, not the Function URL. A slice's Lambda reaches Amazon
2269
+ // Location through the SDK now, so what it needs is the index's name — and
2270
+ // the grant that goes with it, which is why this is also handed to
2271
+ // `registerGeocoderPlaceIndex` rather than only to the environment. The
2272
+ // browser keeps using the Function URL until the platform API carries a
2273
+ // geocode field; the two doors are independent.
2274
+ let geocoderPlaceIndex: Pulumi.Output.t<string> = switch platformStackRef {
2255
2275
  | Some(stackRef) =>
2256
2276
  (
2257
- stackRef->Pulumi.StackReference.getOutput("geocoderEndpoint"):
2277
+ stackRef->Pulumi.StackReference.getOutput("geocoderPlaceIndex"):
2258
2278
  Pulumi.Output.t<option<string>>
2259
2279
  )->Pulumi.Output.apply(o => o->Option.getOr(""))
2260
- | None => geocoderEndpointRef.contents
2280
+ | None => geocoderPlaceIndexRef.contents
2261
2281
  }
2262
- PluginRuntime_Builder.registerCapabilityEnv("GEOCODER_ENDPOINT", geocoderEndpoint)
2282
+ PluginRuntime_Builder.registerCapabilityEnv("PLACE_INDEX_NAME", geocoderPlaceIndex)
2283
+ PluginRuntime_Builder.registerGeocoderPlaceIndex(geocoderPlaceIndex)
2263
2284
 
2264
2285
  module P = unpack(plugin)
2265
2286
  let pluginComponent = P.make()
@@ -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,7 +130,7 @@ function getObjectStoreEndpoints() {
130
130
  return objectStoreEndpointsRef.contents;
131
131
  }
132
132
 
133
- let geocoderEndpointRef = {
133
+ let geocoderPlaceIndexRef = {
134
134
  contents: Pulumi.output("")
135
135
  };
136
136
 
@@ -189,9 +189,9 @@ function MakeWithConfig(Config) {
189
189
  }
190
190
  };
191
191
  let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
192
- types: Platform_AdminApi$ReventlessCore.uploadTypes,
192
+ types: Platform_AdminApi$ReventlessCore.uploadTypes.concat(Platform_AdminApi$ReventlessCore.geocodeTypes),
193
193
  mutations: Platform_AdminApi$ReventlessCore.uploadMutationFields,
194
- queries: [" Platform_ping: String"],
194
+ queries: [" Platform_ping: String"].concat(Platform_AdminApi$ReventlessCore.geocodeQueryFields),
195
195
  subscriptions: [],
196
196
  subscriptionSources: []
197
197
  });
@@ -1127,10 +1127,13 @@ function MakeWithConfig(Config) {
1127
1127
  let cognitoPool = Platform_Stack$ReventlessAws.resolveCognitoUserPool();
1128
1128
  let domainEventsEndpointOutput = domainEventsApiOpt !== undefined ? AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt).apply(ep => ep + "/event") : Pulumi.output(undefined);
1129
1129
  let index = hostUiBundle.geocoderPlaceIndex;
1130
- let geocoderEndpointOutput = index !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(index.indexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
1131
- let geocoderEndpointFlat = geocoderEndpointOutput.apply(o => Stdlib_Option.getOr(o, ""));
1132
- Pulumi$Pulumi.$$export("geocoderEndpoint", geocoderEndpointFlat);
1133
- geocoderEndpointRef.contents = geocoderEndpointFlat;
1130
+ if (index !== undefined && Config.splitApi) {
1131
+ Geocoder_AwsLocation_Resolver$ReventlessAws.make(domainApi, index.indexName, undefined, {});
1132
+ }
1133
+ let index$1 = hostUiBundle.geocoderPlaceIndex;
1134
+ let geocoderPlaceIndexFlat = index$1 !== undefined ? index$1.indexName : Pulumi.output("");
1135
+ Pulumi$Pulumi.$$export("geocoderPlaceIndex", geocoderPlaceIndexFlat);
1136
+ geocoderPlaceIndexRef.contents = geocoderPlaceIndexFlat;
1134
1137
  let configJsonContent = Pulumi.all([
1135
1138
  Pulumi.all([
1136
1139
  resolvedDomainApiEndpoint,
@@ -1138,10 +1141,8 @@ function MakeWithConfig(Config) {
1138
1141
  cognitoPool.poolId,
1139
1142
  cognitoPool.clientId
1140
1143
  ]),
1141
- domainEventsEndpointOutput,
1142
- geocoderEndpointOutput
1144
+ domainEventsEndpointOutput
1143
1145
  ]).apply(param => {
1144
- let geocoderEpOpt = param[2];
1145
1146
  let eventsEpOpt = param[1];
1146
1147
  let match = param[0];
1147
1148
  let computed = [
@@ -1188,11 +1189,7 @@ function MakeWithConfig(Config) {
1188
1189
  AppSync_EventsApi$ReventlessAws.clientNamespaceName
1189
1190
  ]
1190
1191
  ]) : computed;
1191
- let withGeocoder = geocoderEpOpt !== undefined ? withEvents.concat([[
1192
- "geocoderEndpoint",
1193
- geocoderEpOpt
1194
- ]]) : withEvents;
1195
- 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));
1196
1193
  });
1197
1194
  new (Aws.s3.BucketObject)("host-ui-config-json", {
1198
1195
  bucket: bucketName,
@@ -1247,8 +1244,9 @@ function MakeWithConfig(Config) {
1247
1244
  bytes: bytes.length
1248
1245
  };
1249
1246
  });
1250
- let geocoderEndpoint = platformStackRef !== undefined ? Primitive_option.valFromOption(platformStackRef).getOutput("geocoderEndpoint").apply(o => Stdlib_Option.getOr(o, "")) : geocoderEndpointRef.contents;
1251
- PluginRuntime_Builder$ReventlessAws.registerCapabilityEnv("GEOCODER_ENDPOINT", geocoderEndpoint);
1247
+ let geocoderPlaceIndex = platformStackRef !== undefined ? Primitive_option.valFromOption(platformStackRef).getOutput("geocoderPlaceIndex").apply(o => Stdlib_Option.getOr(o, "")) : geocoderPlaceIndexRef.contents;
1248
+ PluginRuntime_Builder$ReventlessAws.registerCapabilityEnv("PLACE_INDEX_NAME", geocoderPlaceIndex);
1249
+ PluginRuntime_Builder$ReventlessAws.registerGeocoderPlaceIndex(geocoderPlaceIndex);
1252
1250
  let pluginComponent = plugin.make();
1253
1251
  Plugin_Helpers$ReventlessCore.clearOffload();
1254
1252
  currentDeployTarget.contents = "Domain";
@@ -1443,9 +1441,9 @@ function Make($star) {
1443
1441
  }
1444
1442
  };
1445
1443
  let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
1446
- types: Platform_AdminApi$ReventlessCore.uploadTypes,
1444
+ types: Platform_AdminApi$ReventlessCore.uploadTypes.concat(Platform_AdminApi$ReventlessCore.geocodeTypes),
1447
1445
  mutations: Platform_AdminApi$ReventlessCore.uploadMutationFields,
1448
- queries: [" Platform_ping: String"],
1446
+ queries: [" Platform_ping: String"].concat(Platform_AdminApi$ReventlessCore.geocodeQueryFields),
1449
1447
  subscriptions: [],
1450
1448
  subscriptionSources: []
1451
1449
  });
@@ -2358,10 +2356,13 @@ function Make($star) {
2358
2356
  let cognitoPool = Platform_Stack$ReventlessAws.resolveCognitoUserPool();
2359
2357
  let domainEventsEndpointOutput = domainEventsApiOpt !== undefined ? AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt).apply(ep => ep + "/event") : Pulumi.output(undefined);
2360
2358
  let index = hostUiBundle.geocoderPlaceIndex;
2361
- let geocoderEndpointOutput = index !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(index.indexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
2362
- let geocoderEndpointFlat = geocoderEndpointOutput.apply(o => Stdlib_Option.getOr(o, ""));
2363
- Pulumi$Pulumi.$$export("geocoderEndpoint", geocoderEndpointFlat);
2364
- geocoderEndpointRef.contents = geocoderEndpointFlat;
2359
+ if (index !== undefined) {
2360
+ Geocoder_AwsLocation_Resolver$ReventlessAws.make(domainApi, index.indexName, undefined, {});
2361
+ }
2362
+ let index$1 = hostUiBundle.geocoderPlaceIndex;
2363
+ let geocoderPlaceIndexFlat = index$1 !== undefined ? index$1.indexName : Pulumi.output("");
2364
+ Pulumi$Pulumi.$$export("geocoderPlaceIndex", geocoderPlaceIndexFlat);
2365
+ geocoderPlaceIndexRef.contents = geocoderPlaceIndexFlat;
2365
2366
  let configJsonContent = Pulumi.all([
2366
2367
  Pulumi.all([
2367
2368
  resolvedDomainApiEndpoint,
@@ -2369,10 +2370,8 @@ function Make($star) {
2369
2370
  cognitoPool.poolId,
2370
2371
  cognitoPool.clientId
2371
2372
  ]),
2372
- domainEventsEndpointOutput,
2373
- geocoderEndpointOutput
2373
+ domainEventsEndpointOutput
2374
2374
  ]).apply(param => {
2375
- let geocoderEpOpt = param[2];
2376
2375
  let eventsEpOpt = param[1];
2377
2376
  let match = param[0];
2378
2377
  let computed = [
@@ -2419,11 +2418,7 @@ function Make($star) {
2419
2418
  AppSync_EventsApi$ReventlessAws.clientNamespaceName
2420
2419
  ]
2421
2420
  ]) : computed;
2422
- let withGeocoder = geocoderEpOpt !== undefined ? withEvents.concat([[
2423
- "geocoderEndpoint",
2424
- geocoderEpOpt
2425
- ]]) : withEvents;
2426
- 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));
2427
2422
  });
2428
2423
  new (Aws.s3.BucketObject)("host-ui-config-json", {
2429
2424
  bucket: bucketName,
@@ -2478,8 +2473,9 @@ function Make($star) {
2478
2473
  bytes: bytes.length
2479
2474
  };
2480
2475
  });
2481
- let geocoderEndpoint = platformStackRef !== undefined ? Primitive_option.valFromOption(platformStackRef).getOutput("geocoderEndpoint").apply(o => Stdlib_Option.getOr(o, "")) : geocoderEndpointRef.contents;
2482
- PluginRuntime_Builder$ReventlessAws.registerCapabilityEnv("GEOCODER_ENDPOINT", geocoderEndpoint);
2476
+ let geocoderPlaceIndex = platformStackRef !== undefined ? Primitive_option.valFromOption(platformStackRef).getOutput("geocoderPlaceIndex").apply(o => Stdlib_Option.getOr(o, "")) : geocoderPlaceIndexRef.contents;
2477
+ PluginRuntime_Builder$ReventlessAws.registerCapabilityEnv("PLACE_INDEX_NAME", geocoderPlaceIndex);
2478
+ PluginRuntime_Builder$ReventlessAws.registerGeocoderPlaceIndex(geocoderPlaceIndex);
2483
2479
  let pluginComponent = plugin.make();
2484
2480
  Plugin_Helpers$ReventlessCore.clearOffload();
2485
2481
  currentDeployTarget.contents = "Domain";
@@ -2631,7 +2627,7 @@ export {
2631
2627
  getSplitApiOutputs,
2632
2628
  objectStoreEndpointsRef,
2633
2629
  getObjectStoreEndpoints,
2634
- geocoderEndpointRef,
2630
+ geocoderPlaceIndexRef,
2635
2631
  MakeWithConfig,
2636
2632
  Make,
2637
2633
  }
@@ -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
+ }