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

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,20 @@
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.267 (2026-08-04)
7
+
8
+ ### Features
9
+
10
+ * **admin:** serve the complete plugin structure to developer tooling ([e99cda0](https://github.com/ReventlessDev/reventless-core/commit/e99cda02a2f862b36bcfdb018a0fb34bb7426a2b))
11
+
12
+
13
+ # 3.0.0-alpha.266 (2026-08-04)
14
+
15
+ ### Features
16
+
17
+ * **geocoding:** serve geocoding through the platform GraphQL API (D9 half 2) ([914d3c0](https://github.com/ReventlessDev/reventless-core/commit/914d3c0a99a1b67ffb89e2b8259dd30f67ff9bae))
18
+
19
+
6
20
  # 3.0.0-alpha.265 (2026-08-04)
7
21
 
8
22
  ### 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.267",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -17,12 +17,12 @@
17
17
  "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.65",
18
18
  "@reventlessdev/rescript-node": "2.0.0-alpha.1",
19
19
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
20
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.126",
21
+ "@reventlessdev/reventless-core": "3.0.0-alpha.212",
22
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.30",
23
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.76",
20
24
  "@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",
24
- "@reventlessdev/reventless-spec": "3.0.0-alpha.100",
25
- "@reventlessdev/reventless-interop": "3.0.0-alpha.30"
25
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.100"
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
@@ -1449,6 +1452,7 @@ module MakeWithConfig = (
1449
1452
  ~api=platformApi,
1450
1453
  ~pluginReadModelTableName=tableName,
1451
1454
  ~offloadBucketName,
1455
+ ~schemaReady=admin.adminSchemaPushed,
1452
1456
  ~opts={},
1453
1457
  )
1454
1458
  | None => ()
@@ -2066,35 +2070,32 @@ module MakeWithConfig = (
2066
2070
  | None => Pulumi.Output.make(None)
2067
2071
  }
2068
2072
 
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(
2073
+ // The geocoding capability's client door (D9 half 2): a `Query.geocode`
2074
+ // resolver on the domain API, provisioned only when a place index is
2075
+ // configured. Authenticated by the API's Cognito authorizer, so it replaces
2076
+ // the public unauthenticated Function URL the browser used to call — after
2077
+ // which that endpoint has no callers at all (the unattended slice path reaches
2078
+ // the same index through the SDK, via `geocoderPlaceIndex` below).
2079
+ //
2080
+ // Split-API only, for the same reason the upload resolvers are: `geocode`
2081
+ // lives on the domain base document (`domainBaseFragment`), which a
2082
+ // unified-mode deployment's single API does not carry.
2083
+ switch cfg.geocoderPlaceIndex {
2084
+ | Some(index) if Config.splitApi =>
2085
+ let _geocodeService = Geocoder_AwsLocation_Resolver.make(
2086
+ ~api=domainApi,
2075
2087
  ~placeIndexName=index.indexName,
2076
- ).url->Pulumi.Output.apply(u => Some(u))
2077
- | None => Pulumi.Output.make(None)
2088
+ ~opts={},
2089
+ )
2090
+ | _ => ()
2078
2091
  }
2079
2092
 
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.
2093
+ // The place index itself, exported so the unattended slice path can reach the
2094
+ // capability: a slice's Lambda signs an SDK call directly and needs the index
2095
+ // name, not an endpoint (no proxy hop, no dependence on a public URL). The
2096
+ // browser reaches the same capability through the `Query.geocode` resolver
2097
+ // above. `""` when unset, so a plugin stack reading this output has one shape
2098
+ // to handle rather than two.
2098
2099
  let geocoderPlaceIndexFlat = switch cfg.geocoderPlaceIndex {
2099
2100
  | Some(index) => index.indexName->Pulumi.Output.fromInput
2100
2101
  | None => Pulumi.Output.make("")
@@ -2116,14 +2117,9 @@ module MakeWithConfig = (
2116
2117
  cognitoPool.clientId,
2117
2118
  )->Pulumi.Output.all4,
2118
2119
  domainEventsEndpointOutput,
2119
- geocoderEndpointOutput,
2120
2120
  )
2121
- ->Pulumi.Output.all3
2122
- ->Pulumi.Output.apply(((
2123
- (domainEp, platformEp, poolId, clientId),
2124
- eventsEpOpt,
2125
- geocoderEpOpt,
2126
- )) => {
2121
+ ->Pulumi.Output.all2
2122
+ ->Pulumi.Output.apply((((domainEp, platformEp, poolId, clientId), eventsEpOpt)) => {
2127
2123
  let computed = [
2128
2124
  ("apiEndpoint", JSON.Encode.string(domainEp)),
2129
2125
  ("platformApiEndpoint", JSON.Encode.string(platformEp)),
@@ -2151,12 +2147,12 @@ module MakeWithConfig = (
2151
2147
  )
2152
2148
  | None => computed
2153
2149
  }
2154
- let withGeocoder = switch geocoderEpOpt {
2155
- | Some(ep) => Array.concat(withEvents, [("geocoderEndpoint", JSON.Encode.string(ep))])
2156
- | None => withEvents
2157
- }
2150
+ // `geocoderEndpoint` is no longer written: the browser reaches geocoding
2151
+ // through the platform API's `Query.geocode` resolver (D9 half 2), the
2152
+ // same way uploads moved off a per-store URL onto `Upload_Presign`. The
2153
+ // public Function URL and its config key are both gone.
2158
2154
  Util_ShellConfig.fields(
2159
- ~computed=withGeocoder,
2155
+ ~computed=withEvents,
2160
2156
  ~viewModes=?cfg.viewModes,
2161
2157
  ~shellConfig=?cfg.shellConfig,
2162
2158
  )
@@ -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
  });
@@ -865,7 +861,7 @@ function MakeWithConfig(Config) {
865
861
  Pulumi$Pulumi.$$export("offloadBucket", offloadBucketName);
866
862
  if (pluginReadModelTableName !== undefined) {
867
863
  AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName);
868
- Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, {});
864
+ Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, admin.adminSchemaPushed, {});
869
865
  }
870
866
  let rm = admin.stateViewSlicesOutputs["UiFragments"];
871
867
  if (rm !== undefined) {
@@ -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
  });
@@ -2107,7 +2096,7 @@ function Make($star) {
2107
2096
  Pulumi$Pulumi.$$export("offloadBucket", offloadBucketName);
2108
2097
  if (pluginReadModelTableName !== undefined) {
2109
2098
  AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName);
2110
- Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, {});
2099
+ Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, admin.adminSchemaPushed, {});
2111
2100
  }
2112
2101
  let rm = admin.stateViewSlicesOutputs["UiFragments"];
2113
2102
  if (rm !== undefined) {
@@ -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,
@@ -24,10 +24,29 @@ export function response(ctx) {
24
24
  }
25
25
  `->Pulumi.Input.make
26
26
 
27
+ // `Platform_PluginStructures` — the same scan, unfiltered. The `complete` flag is
28
+ // the whole difference; see the handler's `toEntryWith`.
29
+ let completeResolverCode = `
30
+ import { util } from '@aws-appsync/utils';
31
+ export function request(ctx) {
32
+ return { operation: 'Invoke', payload: { complete: true } };
33
+ }
34
+ export function response(ctx) {
35
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
36
+ return ctx.result;
37
+ }
38
+ `->Pulumi.Input.make
39
+
27
40
  let make = (
28
41
  ~api: Pulumi.Output.t<AppSync.GraphQLApi.t>,
29
42
  ~pluginReadModelTableName: Pulumi.Output.t<string>,
30
43
  ~offloadBucketName: Pulumi.Output.t<string>,
44
+ // Resolves once the admin schema push is ACTIVE (Platform_Admin.adminSchemaPushed).
45
+ // Only the `Platform_PluginStructures` resolver is gated on it: that field is new,
46
+ // so CreateResolver would otherwise race StartSchemaCreation on the deploy that
47
+ // first ships it. `Platform_ComponentDefinitions` predates the gate and is left
48
+ // ungated so this change adds no dependency edge to an already-deployed resource.
49
+ ~schemaReady: Pulumi.Output.t<unit>,
31
50
  ~opts: Pulumi.ComponentResource.options,
32
51
  ) => {
33
52
  let opts = opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
@@ -215,4 +234,21 @@ let make = (
215
234
  ~code=resolverCode,
216
235
  ~opts,
217
236
  )
237
+
238
+ // Second field, same DataSource and same Lambda — the developer-tooling read of
239
+ // the identical scan. Sharing the data source is what keeps the two answers from
240
+ // drifting: there is one place that decides what a deployed plugin's structure is.
241
+ // Created only after the schema carrying `Platform_PluginStructures` is ACTIVE.
242
+ let _structuresResolver =
243
+ schemaReady->Pulumi.Output.apply(() =>
244
+ AppSync_Resolver_Native.makeUnitJsResolver(
245
+ ~name=name ++ "StructuresResolver",
246
+ ~api,
247
+ ~dataSourceName=dataSource.name->Pulumi.Output.asInput,
248
+ ~type_="Query"->Pulumi.Input.make,
249
+ ~field="Platform_PluginStructures"->Pulumi.Input.make,
250
+ ~code=completeResolverCode,
251
+ ~opts,
252
+ )
253
+ )
218
254
  }
@@ -27,7 +27,18 @@ export function response(ctx) {
27
27
  }
28
28
  `;
29
29
 
30
- function make(api, pluginReadModelTableName, offloadBucketName, opts) {
30
+ let completeResolverCode = `
31
+ import { util } from '@aws-appsync/utils';
32
+ export function request(ctx) {
33
+ return { operation: 'Invoke', payload: { complete: true } };
34
+ }
35
+ export function response(ctx) {
36
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
37
+ return ctx.result;
38
+ }
39
+ `;
40
+
41
+ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, opts) {
31
42
  let opts$1 = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
32
43
  let name = "PlatformUIDefinitions";
33
44
  let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "Lambda", Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name + "Lambda", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
@@ -131,10 +142,12 @@ function make(api, pluginReadModelTableName, offloadBucketName, opts) {
131
142
  serviceRoleArn: dataSourceRole.arn
132
143
  }, opts$1);
133
144
  AppSync_Resolver_Native$ReventlessAws.makeUnitJsResolver(name + "Resolver", api, dataSource.name, "Query", "Platform_ComponentDefinitions", resolverCode, opts$1);
145
+ schemaReady.apply(() => AppSync_Resolver_Native$ReventlessAws.makeUnitJsResolver(name + "StructuresResolver", api, dataSource.name, "Query", "Platform_PluginStructures", completeResolverCode, opts$1));
134
146
  }
135
147
 
136
148
  export {
137
149
  resolverCode,
150
+ completeResolverCode,
138
151
  make,
139
152
  }
140
153
  /* @pulumi/aws Not a pure module */
@@ -43,11 +43,21 @@ let filterStructure = (structure: JSON.t): option<dict<JSON.t>> =>
43
43
  Some(out)
44
44
  }
45
45
 
46
- // `{pluginId: name, ...filterStructure(structure)}` — pluginId first so the
47
- // structure's fields win on the (never-expected) key collision, matching the JS
48
- // spread. Drops a row with no decodable structure (former `!item.structure`).
49
- let toEntry = (item: dict<JSON.t>, ~name: string): option<JSON.t> =>
50
- switch item->Dict.get("structure")->Option.flatMap(filterStructure) {
46
+ // `{pluginId: name, ...structure}` — pluginId first so the structure's fields win
47
+ // on the (never-expected) key collision, matching the JS spread. Drops a row with
48
+ // no decodable structure (former `!item.structure`).
49
+ //
50
+ // `~filter` is what separates the two fields this handler serves: AutoUI
51
+ // (`Platform_ComponentDefinitions`) gets the public-queryable subset, developer
52
+ // tooling (`Platform_PluginStructures`) gets the structure exactly as persisted,
53
+ // including Internal components and the producer-side `extensionPoints` that the
54
+ // AutoUI entry type does not declare.
55
+ let toEntryWith = (~filter: bool, item: dict<JSON.t>, ~name: string): option<JSON.t> => {
56
+ let structure =
57
+ item
58
+ ->Dict.get("structure")
59
+ ->Option.flatMap(s => filter ? filterStructure(s) : s->JSON.Decode.object)
60
+ switch structure {
51
61
  | None => None
52
62
  | Some(structureObj) =>
53
63
  let entry = Dict.make()
@@ -55,10 +65,17 @@ let toEntry = (item: dict<JSON.t>, ~name: string): option<JSON.t> =>
55
65
  structureObj->Dict.toArray->Array.forEach(((k, v)) => entry->Dict.set(k, v))
56
66
  Some(JSON.Encode.object(entry))
57
67
  }
68
+ }
69
+
58
70
 
59
71
  // The built-in Platform_Admin entry, injected at deploy time as the
60
72
  // ADMIN_ENTRY_JSON env var (the admin never Connects to itself, so its structure
61
73
  // never enters the Plugin read model).
74
+ //
75
+ // Shared by both fields. The admin structure has no Internal components and no
76
+ // extension points, so its filtered and complete encodings carry the same
77
+ // components; the structure-level fields the complete entry adds are absent from
78
+ // this JSON and resolve to null, which is what `extensionPoints: None` means.
62
79
  let adminEntry: option<JSON.t> =
63
80
  switch NodeProcess.env->Dict.get("ADMIN_ENTRY_JSON") {
64
81
  | Some(s) if s != "" => Some(JSON.parseOrThrow(s))
@@ -94,7 +111,21 @@ let resolveStructure = (
94
111
  }
95
112
  }
96
113
 
97
- let handler = async (_event: JSON.t): array<JSON.t> => {
114
+ // One Lambda serves both fields the scan, the offload resolution and the
115
+ // latest-version-per-plugin collapse are identical work, and duplicating them into
116
+ // a second function would mean a second cold start and a second chance for the two
117
+ // to disagree about what "the deployed structure" is. The resolver for
118
+ // `Platform_PluginStructures` invokes with `{complete: true}`.
119
+ let isComplete = (event: JSON.t): bool =>
120
+ event
121
+ ->JSON.Decode.object
122
+ ->Option.flatMap(o => o->Dict.get("complete"))
123
+ ->Option.flatMap(JSON.Decode.bool)
124
+ ->Option.getOr(false)
125
+
126
+ let handler = async (event: JSON.t): array<JSON.t> => {
127
+ let complete = isComplete(event)
128
+ let toEntry = (item, ~name) => toEntryWith(~filter=!complete, item, ~name)
98
129
  let admin = adminEntry->Option.mapOr([], e => [e])
99
130
  switch NodeProcess.env->Dict.get("PLUGIN_RM_TABLE") {
100
131
  | None | Some("") =>
@@ -31,14 +31,20 @@ function filterStructure(structure) {
31
31
  return out;
32
32
  }
33
33
 
34
- function toEntry(item, name) {
35
- let structureObj = Stdlib_Option.flatMap(item["structure"], filterStructure);
36
- if (structureObj === undefined) {
34
+ function toEntryWith(filter, item, name) {
35
+ let structure = Stdlib_Option.flatMap(item["structure"], s => {
36
+ if (filter) {
37
+ return filterStructure(s);
38
+ } else {
39
+ return Stdlib_JSON.Decode.object(s);
40
+ }
41
+ });
42
+ if (structure === undefined) {
37
43
  return;
38
44
  }
39
45
  let entry = {};
40
46
  entry["pluginId"] = name;
41
- Object.entries(structureObj).forEach(param => {
47
+ Object.entries(structure).forEach(param => {
42
48
  entry[param[0]] = param[1];
43
49
  });
44
50
  return entry;
@@ -65,7 +71,13 @@ function resolveStructure(fetch, item) {
65
71
  }
66
72
  }
67
73
 
68
- async function handler(_event) {
74
+ function isComplete(event) {
75
+ return Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(event), o => o["complete"]), Stdlib_JSON.Decode.bool), false);
76
+ }
77
+
78
+ async function handler(event) {
79
+ let complete = isComplete(event);
80
+ let toEntry = (item, name) => toEntryWith(!complete, item, name);
69
81
  let admin = Stdlib_Option.mapOr(adminEntry, [], e => [e]);
70
82
  let table = process.env["PLUGIN_RM_TABLE"];
71
83
  if (table !== undefined) {
@@ -94,9 +106,10 @@ export {
94
106
  str,
95
107
  isPublicQueryable,
96
108
  filterStructure,
97
- toEntry,
109
+ toEntryWith,
98
110
  adminEntry,
99
111
  resolveStructure,
112
+ isComplete,
100
113
  handler,
101
114
  }
102
115
  /* s Not a pure module */