@reventlessdev/reventless-aws 3.0.0-alpha.251 → 3.0.0-alpha.252

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,17 @@
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.252 (2026-08-01)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **aws:** let a dead letter be seen ([e64949b](https://github.com/ReventlessDev/reventless-core/commit/e64949bb3f4965cb8e6fc7a2f04971290ebe43bf))
11
+ * **aws:** let the Function URL own CORS on the upload and geocoder services ([19d4b7f](https://github.com/ReventlessDev/reventless-core/commit/19d4b7f26f0925b7c659a1b37555e952bb7c7f46))
12
+ ### Features
13
+
14
+ * **upload:** add the release half of the upload contract via the domain API ([448f887](https://github.com/ReventlessDev/reventless-core/commit/448f88714a5875be420f794a08d482fcf4ba8404))
15
+
16
+
6
17
  # 3.0.0-alpha.251 (2026-08-01)
7
18
 
8
19
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.251",
3
+ "version": "3.0.0-alpha.252",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -18,11 +18,11 @@
18
18
  "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.62",
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.198",
21
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.116",
22
22
  "@reventlessdev/reventless-interop": "3.0.0-alpha.30",
23
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.62",
24
- "@reventlessdev/reventless-infra": "3.0.0-alpha.115",
25
- "@reventlessdev/reventless-spec": "3.0.0-alpha.90"
23
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.91",
24
+ "@reventlessdev/reventless-core": "3.0.0-alpha.199",
25
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.63"
26
26
  },
27
27
  "devDependencies": {
28
28
  "rescript": "12.3.0",
package/src/Platform.res CHANGED
@@ -67,7 +67,6 @@ type objectStoreEndpoint = {
67
67
  // only known after the bucket resolves — and a plain string here is exactly
68
68
  // how this shipped exporting a bucket name that no ARN matches.
69
69
  bucketName: Pulumi.Output.t<string>,
70
- uploadUrl: Pulumi.Output.t<string>,
71
70
  // Public base URL the store is served from, when the platform serves it
72
71
  // itself. `None` when a host-UI bundle is deployed — there the shell's own
73
72
  // origin serves the store same-origin and a relative `/{prefix}/…` resolves
@@ -170,9 +169,15 @@ module MakeWithConfig = (
170
169
  // "Relay node resolution" section of the merged-api plan (never resolved on
171
170
  // any deployed AWS platform, zero consumers; the Node interface and global
172
171
  // IDs stay).
172
+ // The upload service (route B) lives on the domain base, not the Admin-gated admin
173
+ // base: `Upload_Presign`/`Upload_Release` take the domain API's default
174
+ // `AllowAuthenticated` auth so any authenticated user can mint/release their own
175
+ // uploads. See [docs/plans/upload-release-path.md] § "Which API". (Split mode only —
176
+ // unified mode carries the admin base here instead; it declares no stores in practice,
177
+ // so the upload resolvers below are never created there.)
173
178
  let domainBaseFragment = ReventlessCore.GraphQL_Stitcher.encode({
174
- types: [],
175
- mutations: [],
179
+ types: ReventlessCore.Platform_AdminApi.uploadTypes,
180
+ mutations: ReventlessCore.Platform_AdminApi.uploadMutationFields,
176
181
  queries: [" Platform_ping: String"],
177
182
  subscriptions: [],
178
183
  subscriptionSources: [],
@@ -1585,17 +1590,14 @@ module MakeWithConfig = (
1585
1590
  b
1586
1591
  }
1587
1592
  let storeHandle = bucket->Capability_ObjectStore_S3.underPrefix(~keyPrefix)
1588
- // One presign service per store, scoped to that store's own prefix.
1589
- let presign = Upload_Presign_S3.make(
1590
- ~name=`UploadPresign-${plugin}-${store}`,
1591
- ~bucketName=storeHandle.bucketName,
1592
- ~servedPrefix=storeHandle.keyPrefix,
1593
- )
1594
1593
  // `bucketName` is the layout's *logical* name and groups the served view
1595
1594
  // below; `storeHandle.bucketName` is the physical one Pulumi resolves, and
1596
1595
  // is what a consumer needs to build an ARN. Carrying both is the point —
1597
1596
  // they are different strings and only one of them exists in S3.
1598
- (`${plugin}.${store}`, keyPrefix, bucketName, storeHandle.bucketName, presign.url)
1597
+ //
1598
+ // Presigning is no longer per store: one platform-API Upload service (below)
1599
+ // covers every declared store, keyed by the qualified `{plugin}.{store}` name.
1600
+ (`${plugin}.${store}`, keyPrefix, bucketName, storeHandle.bucketName)
1599
1601
  })
1600
1602
 
1601
1603
  // Group the served view by bucket: one origin and one bucket policy per
@@ -1606,7 +1608,7 @@ module MakeWithConfig = (
1606
1608
  ->Option.map(b => {
1607
1609
  ReventlessInfra.Platform.id: bucketName,
1608
1610
  prefixes: declaredStoreServices
1609
- ->Array.filterMap(((_, prefix, bn, _, _)) => bn == bucketName ? Some(prefix) : None),
1611
+ ->Array.filterMap(((_, prefix, bn, _)) => bn == bucketName ? Some(prefix) : None),
1610
1612
  bucketId: b.bucketId,
1611
1613
  bucketArn: b.bucketArn,
1612
1614
  bucketRegionalDomainName: b.bucketRegionalDomainName,
@@ -1645,30 +1647,58 @@ module MakeWithConfig = (
1645
1647
  keyPrefix,
1646
1648
  _logicalBucketName,
1647
1649
  physicalBucketName,
1648
- uploadUrl,
1649
1650
  )) => {
1650
1651
  store,
1651
1652
  keyPrefix,
1652
1653
  bucketName: physicalBucketName->Pulumi.Output.fromInput,
1653
- uploadUrl,
1654
1654
  baseUrl: storeServingBaseUrl,
1655
1655
  })
1656
1656
  objectStoreEndpointsRef := declaredStoreEndpoints
1657
1657
 
1658
+ // One domain-API Upload service (route B1) covers every declared store plus the
1659
+ // legacy `hostUiBundle.uploadBucket`, keyed by the qualified `{plugin}.{store}` name
1660
+ // the caller passes as the `store` argument. On the domain API (not the Admin-gated
1661
+ // platform API — see the plan's "Which API" note) so any authenticated user can
1662
+ // mint/release. Created here in the outer scope — not inside the `hostUiBundle`
1663
+ // branch — because a platform whose UI ships from another stack still has declared
1664
+ // stores that need presigning. `uploads` keys the legacy store, matching the prefix
1665
+ // it has always served under.
1666
+ let uploadServiceStores =
1667
+ declaredStoreServices
1668
+ ->Array.map(((qualified, keyPrefix, _logical, physicalBucketName)) => {
1669
+ Upload_Presign_S3.qualified: qualified,
1670
+ bucketName: physicalBucketName,
1671
+ servedPrefix: keyPrefix,
1672
+ })
1673
+ ->Array.concat(
1674
+ switch hostUiBundle->Option.flatMap(c => c.uploadBucket) {
1675
+ | Some(store) => [
1676
+ {
1677
+ Upload_Presign_S3.qualified: store.keyPrefix,
1678
+ bucketName: store.bucketName,
1679
+ servedPrefix: store.keyPrefix,
1680
+ },
1681
+ ]
1682
+ | None => []
1683
+ },
1684
+ )
1685
+ if uploadServiceStores->Array.length > 0 {
1686
+ let _uploadService = Upload_Presign_S3.make(
1687
+ ~api=domainApi,
1688
+ ~stores=uploadServiceStores,
1689
+ ~opts={},
1690
+ )
1691
+ }
1692
+
1658
1693
  // Exported unconditionally — the provisioning above already is. Their only
1659
1694
  // consumers used to sit inside `switch hostUiBundle`, which left a platform
1660
1695
  // whose UI ships from another stack holding stores it could not name.
1661
1696
  //
1662
1697
  // Omitted entirely when nothing is declared, so a deployment with no
1663
- // declared store keeps a byte-identical output set.
1698
+ // declared store keeps a byte-identical output set. The presign endpoint is no
1699
+ // longer exported — under route B the client calls the platform API's
1700
+ // `Upload_Presign` mutation, so there is no per-store URL to publish.
1664
1701
  if declaredStoreEndpoints->Array.length > 0 {
1665
- Pulumi.Pulumi.export(
1666
- "uploadEndpoints",
1667
- declaredStoreEndpoints
1668
- ->Array.map(e => e.uploadUrl->Pulumi.Output.apply(u => (e.store, JSON.Encode.string(u))))
1669
- ->Pulumi.Output.all
1670
- ->Pulumi.Output.apply(pairs => pairs->Dict.fromArray->JSON.Encode.object),
1671
- )
1672
1702
  Pulumi.Pulumi.export(
1673
1703
  "objectStores",
1674
1704
  declaredStoreEndpoints
@@ -1826,27 +1856,11 @@ module MakeWithConfig = (
1826
1856
  | None => Pulumi.Output.make(None)
1827
1857
  }
1828
1858
 
1829
- // Optional public upload presign Function URL, provisioned against the
1830
- // configured store; its resolved URL becomes config.json's
1831
- // `uploadEndpoint`. No store Output<None> field omitted below.
1832
- let uploadEndpointOutput: Pulumi.Output.t<option<string>> = switch cfg.uploadBucket {
1833
- | Some(store) =>
1834
- Upload_Presign_S3.make(~bucketName=store.bucketName).url->Pulumi.Output.apply(u => Some(u))
1835
- | None => Pulumi.Output.make(None)
1836
- }
1837
-
1838
- // Per-store presign endpoints, keyed by the store's qualified name. This
1839
- // is what lets an upload input bind to the store its field *declares*
1840
- // rather than inferring one from the field's name — the endpoint exists
1841
- // per store, so there is something concrete to bind to.
1842
- //
1843
- // `uploadEndpoint` (singular) stays as the legacy service's URL: a shell
1844
- // built before per-store binding reads it and is unaffected.
1845
- let storeUploadEndpointsOutput: Pulumi.Output.t<array<(string, string)>> =
1846
- declaredStoreServices
1847
- ->Array.map(((qualified, _, _, _, url)) => url->Pulumi.Output.apply(u => (qualified, u)))
1848
- ->Pulumi.Output.all
1849
-
1859
+ // Presign endpoints are no longer written to config.json: under route B the
1860
+ // client calls the platform API's `Upload_Presign` mutation (reachable from
1861
+ // `platformApiEndpoint`, already present) with the store it declares, so there
1862
+ // is no per-store URL to publish. The store's *serving* base URL still travels
1863
+ // in the `objectStores` stack output above.
1850
1864
  let configJsonContent =
1851
1865
  (
1852
1866
  (
@@ -1857,14 +1871,12 @@ module MakeWithConfig = (
1857
1871
  )->Pulumi.Output.all4,
1858
1872
  domainEventsEndpointOutput,
1859
1873
  geocoderEndpointOutput,
1860
- (uploadEndpointOutput, storeUploadEndpointsOutput)->Pulumi.Output.all2,
1861
1874
  )
1862
- ->Pulumi.Output.all4
1875
+ ->Pulumi.Output.all3
1863
1876
  ->Pulumi.Output.apply(((
1864
1877
  (domainEp, platformEp, poolId, clientId),
1865
1878
  eventsEpOpt,
1866
1879
  geocoderEpOpt,
1867
- (uploadEpOpt, storeUploadEps),
1868
1880
  )) => {
1869
1881
  let fields = [
1870
1882
  ("apiEndpoint", JSON.Encode.string(domainEp)),
@@ -1897,29 +1909,7 @@ module MakeWithConfig = (
1897
1909
  | Some(ep) => Array.concat(withEvents, [("geocoderEndpoint", JSON.Encode.string(ep))])
1898
1910
  | None => withEvents
1899
1911
  }
1900
- let withUpload = switch uploadEpOpt {
1901
- | Some(ep) => Array.concat(withGeocoder, [("uploadEndpoint", JSON.Encode.string(ep))])
1902
- | None => withGeocoder
1903
- }
1904
- // Omitted entirely when nothing is declared, so a deployment without
1905
- // declared stores writes a byte-identical config.json.
1906
- let withStoreUploads = switch storeUploadEps {
1907
- | [] => withUpload
1908
- | eps =>
1909
- Array.concat(
1910
- withUpload,
1911
- [
1912
- (
1913
- "uploadEndpoints",
1914
- eps
1915
- ->Array.map(((qualified, url)) => (qualified, JSON.Encode.string(url)))
1916
- ->Dict.fromArray
1917
- ->JSON.Encode.object,
1918
- ),
1919
- ],
1920
- )
1921
- }
1922
- withStoreUploads->Dict.fromArray->JSON.Encode.object->JSON.stringify
1912
+ withGeocoder->Dict.fromArray->JSON.Encode.object->JSON.stringify
1923
1913
  })
1924
1914
 
1925
1915
  let _ = PulumiAws.S3.BucketObject.make(
@@ -156,8 +156,8 @@ function MakeWithConfig(Config) {
156
156
  };
157
157
  let platformStackRef = Stdlib_Option.map(new Pulumi.Config("platform").get("stack"), stack => new Pulumi.StackReference(stack));
158
158
  let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
159
- types: [],
160
- mutations: [],
159
+ types: Platform_AdminApi$ReventlessCore.uploadTypes,
160
+ mutations: Platform_AdminApi$ReventlessCore.uploadMutationFields,
161
161
  queries: [" Platform_ping: String"],
162
162
  subscriptions: [],
163
163
  subscriptionSources: []
@@ -931,13 +931,11 @@ function MakeWithConfig(Config) {
931
931
  bucket = b$1;
932
932
  }
933
933
  let storeHandle = Capability_ObjectStore_S3$ReventlessAws.underPrefix(bucket, keyPrefix);
934
- let presign = Upload_Presign_S3$ReventlessAws.make(storeHandle.bucketName, undefined, storeHandle.keyPrefix, `UploadPresign-` + plugin + `-` + store, undefined);
935
934
  return [
936
935
  plugin + `.` + store,
937
936
  keyPrefix,
938
937
  bucketName,
939
- storeHandle.bucketName,
940
- presign.url
938
+ storeHandle.bucketName
941
939
  ];
942
940
  });
943
941
  let declaredServedBuckets = Stdlib_Array.filterMap(storeBucketOrder, bucketName => Stdlib_Option.map(storeBuckets[bucketName], b => ({
@@ -966,15 +964,23 @@ function MakeWithConfig(Config) {
966
964
  store: param[0],
967
965
  keyPrefix: param[1],
968
966
  bucketName: param[3],
969
- uploadUrl: param[4],
970
967
  baseUrl: storeServingBaseUrl
971
968
  }));
972
969
  objectStoreEndpointsRef.contents = declaredStoreEndpoints;
970
+ let store = Stdlib_Option.flatMap(hostUiBundle, c => c.uploadBucket);
971
+ let uploadServiceStores = declaredStoreServices.map(param => ({
972
+ qualified: param[0],
973
+ bucketName: param[3],
974
+ servedPrefix: param[1]
975
+ })).concat(store !== undefined ? [{
976
+ qualified: store.keyPrefix,
977
+ bucketName: store.bucketName,
978
+ servedPrefix: store.keyPrefix
979
+ }] : []);
980
+ if (uploadServiceStores.length !== 0) {
981
+ Upload_Presign_S3$ReventlessAws.make(domainApi, uploadServiceStores, undefined, undefined, {});
982
+ }
973
983
  if (declaredStoreEndpoints.length !== 0) {
974
- Pulumi$Pulumi.$$export("uploadEndpoints", Pulumi.all(declaredStoreEndpoints.map(e => e.uploadUrl.apply(u => [
975
- e.store,
976
- u
977
- ]))).apply(pairs => Object.fromEntries(pairs)));
978
984
  Pulumi$Pulumi.$$export("objectStores", Pulumi.all(declaredStoreEndpoints.map(e => Pulumi.all([
979
985
  e.bucketName,
980
986
  Output$Pulumi.allOpt(e.baseUrl)
@@ -1019,14 +1025,14 @@ function MakeWithConfig(Config) {
1019
1025
  } else {
1020
1026
  customDomain = undefined;
1021
1027
  }
1022
- let store = hostUiBundle.uploadBucket;
1028
+ let store$1 = hostUiBundle.uploadBucket;
1023
1029
  let servedBuckets = (
1024
- store !== undefined ? [{
1025
- id: store.keyPrefix,
1026
- prefixes: [store.keyPrefix],
1027
- bucketId: store.bucketId,
1028
- bucketArn: store.bucketArn,
1029
- bucketRegionalDomainName: store.bucketRegionalDomainName
1030
+ store$1 !== undefined ? [{
1031
+ id: store$1.keyPrefix,
1032
+ prefixes: [store$1.keyPrefix],
1033
+ bucketId: store$1.bucketId,
1034
+ bucketArn: store$1.bucketArn,
1035
+ bucketRegionalDomainName: store$1.bucketRegionalDomainName
1030
1036
  }] : []
1031
1037
  ).concat(declaredServedBuckets);
1032
1038
  let match$4 = Plugin_Stack$ReventlessAws.makeUiBundleDistribution("host-ui", Stdlib_Option.getOr(hostUiBundle.bundleVersion, version), Stdlib_Option.getOr(hostUiBundle.assetsDir, Util_Bundle$ReventlessAws.resolvePackageRoot(true, "@reventlessdev/reventless-host-shell") + "/dist"), true, undefined, true, [
@@ -1039,15 +1045,6 @@ function MakeWithConfig(Config) {
1039
1045
  let domainEventsEndpointOutput = domainEventsApiOpt !== undefined ? AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt).apply(ep => ep + "/event") : Pulumi.output(undefined);
1040
1046
  let index = hostUiBundle.geocoderPlaceIndex;
1041
1047
  let geocoderEndpointOutput = index !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(index.indexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
1042
- let store$1 = hostUiBundle.uploadBucket;
1043
- let uploadEndpointOutput = store$1 !== undefined ? Upload_Presign_S3$ReventlessAws.make(store$1.bucketName, undefined, undefined, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
1044
- let storeUploadEndpointsOutput = Pulumi.all(declaredStoreServices.map(param => {
1045
- let qualified = param[0];
1046
- return param[4].apply(u => [
1047
- qualified,
1048
- u
1049
- ]);
1050
- }));
1051
1048
  let configJsonContent = Pulumi.all([
1052
1049
  Pulumi.all([
1053
1050
  resolvedDomainApiEndpoint,
@@ -1056,26 +1053,19 @@ function MakeWithConfig(Config) {
1056
1053
  cognitoPool.clientId
1057
1054
  ]),
1058
1055
  domainEventsEndpointOutput,
1059
- geocoderEndpointOutput,
1060
- Pulumi.all([
1061
- uploadEndpointOutput,
1062
- storeUploadEndpointsOutput
1063
- ])
1056
+ geocoderEndpointOutput
1064
1057
  ]).apply(param => {
1065
- let match = param[3];
1066
- let storeUploadEps = match[1];
1067
- let uploadEpOpt = match[0];
1068
1058
  let geocoderEpOpt = param[2];
1069
1059
  let eventsEpOpt = param[1];
1070
- let match$1 = param[0];
1060
+ let match = param[0];
1071
1061
  let fields = [
1072
1062
  [
1073
1063
  "apiEndpoint",
1074
- match$1[0]
1064
+ match[0]
1075
1065
  ],
1076
1066
  [
1077
1067
  "platformApiEndpoint",
1078
- match$1[1]
1068
+ match[1]
1079
1069
  ],
1080
1070
  [
1081
1071
  "region",
@@ -1087,11 +1077,11 @@ function MakeWithConfig(Config) {
1087
1077
  ],
1088
1078
  [
1089
1079
  "cognitoUserPoolId",
1090
- match$1[2]
1080
+ match[2]
1091
1081
  ],
1092
1082
  [
1093
1083
  "cognitoClientId",
1094
- match$1[3]
1084
+ match[3]
1095
1085
  ],
1096
1086
  [
1097
1087
  "liveUpdates",
@@ -1116,18 +1106,7 @@ function MakeWithConfig(Config) {
1116
1106
  "geocoderEndpoint",
1117
1107
  geocoderEpOpt
1118
1108
  ]]) : withEvents;
1119
- let withUpload = uploadEpOpt !== undefined ? withGeocoder.concat([[
1120
- "uploadEndpoint",
1121
- uploadEpOpt
1122
- ]]) : withGeocoder;
1123
- let withStoreUploads = storeUploadEps.length !== 0 ? withUpload.concat([[
1124
- "uploadEndpoints",
1125
- Object.fromEntries(storeUploadEps.map(param => [
1126
- param[0],
1127
- param[1]
1128
- ]))
1129
- ]]) : withUpload;
1130
- return JSON.stringify(Object.fromEntries(withStoreUploads));
1109
+ return JSON.stringify(Object.fromEntries(withGeocoder));
1131
1110
  });
1132
1111
  new (Aws.s3.BucketObject)("host-ui-config-json", {
1133
1112
  bucket: bucketName,
@@ -1334,8 +1313,8 @@ function Make($star) {
1334
1313
  };
1335
1314
  let platformStackRef = Stdlib_Option.map(new Pulumi.Config("platform").get("stack"), stack => new Pulumi.StackReference(stack));
1336
1315
  let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
1337
- types: [],
1338
- mutations: [],
1316
+ types: Platform_AdminApi$ReventlessCore.uploadTypes,
1317
+ mutations: Platform_AdminApi$ReventlessCore.uploadMutationFields,
1339
1318
  queries: [" Platform_ping: String"],
1340
1319
  subscriptions: [],
1341
1320
  subscriptionSources: []
@@ -2086,13 +2065,11 @@ function Make($star) {
2086
2065
  bucket = b$1;
2087
2066
  }
2088
2067
  let storeHandle = Capability_ObjectStore_S3$ReventlessAws.underPrefix(bucket, keyPrefix);
2089
- let presign = Upload_Presign_S3$ReventlessAws.make(storeHandle.bucketName, undefined, storeHandle.keyPrefix, `UploadPresign-` + plugin + `-` + store, undefined);
2090
2068
  return [
2091
2069
  plugin + `.` + store,
2092
2070
  keyPrefix,
2093
2071
  bucketName,
2094
- storeHandle.bucketName,
2095
- presign.url
2072
+ storeHandle.bucketName
2096
2073
  ];
2097
2074
  });
2098
2075
  let declaredServedBuckets = Stdlib_Array.filterMap(storeBucketOrder, bucketName => Stdlib_Option.map(storeBuckets[bucketName], b => ({
@@ -2121,15 +2098,23 @@ function Make($star) {
2121
2098
  store: param[0],
2122
2099
  keyPrefix: param[1],
2123
2100
  bucketName: param[3],
2124
- uploadUrl: param[4],
2125
2101
  baseUrl: storeServingBaseUrl
2126
2102
  }));
2127
2103
  objectStoreEndpointsRef.contents = declaredStoreEndpoints;
2104
+ let store = Stdlib_Option.flatMap(hostUiBundle, c => c.uploadBucket);
2105
+ let uploadServiceStores = declaredStoreServices.map(param => ({
2106
+ qualified: param[0],
2107
+ bucketName: param[3],
2108
+ servedPrefix: param[1]
2109
+ })).concat(store !== undefined ? [{
2110
+ qualified: store.keyPrefix,
2111
+ bucketName: store.bucketName,
2112
+ servedPrefix: store.keyPrefix
2113
+ }] : []);
2114
+ if (uploadServiceStores.length !== 0) {
2115
+ Upload_Presign_S3$ReventlessAws.make(domainApi, uploadServiceStores, undefined, undefined, {});
2116
+ }
2128
2117
  if (declaredStoreEndpoints.length !== 0) {
2129
- Pulumi$Pulumi.$$export("uploadEndpoints", Pulumi.all(declaredStoreEndpoints.map(e => e.uploadUrl.apply(u => [
2130
- e.store,
2131
- u
2132
- ]))).apply(pairs => Object.fromEntries(pairs)));
2133
2118
  Pulumi$Pulumi.$$export("objectStores", Pulumi.all(declaredStoreEndpoints.map(e => Pulumi.all([
2134
2119
  e.bucketName,
2135
2120
  Output$Pulumi.allOpt(e.baseUrl)
@@ -2174,14 +2159,14 @@ function Make($star) {
2174
2159
  } else {
2175
2160
  customDomain = undefined;
2176
2161
  }
2177
- let store = hostUiBundle.uploadBucket;
2162
+ let store$1 = hostUiBundle.uploadBucket;
2178
2163
  let servedBuckets = (
2179
- store !== undefined ? [{
2180
- id: store.keyPrefix,
2181
- prefixes: [store.keyPrefix],
2182
- bucketId: store.bucketId,
2183
- bucketArn: store.bucketArn,
2184
- bucketRegionalDomainName: store.bucketRegionalDomainName
2164
+ store$1 !== undefined ? [{
2165
+ id: store$1.keyPrefix,
2166
+ prefixes: [store$1.keyPrefix],
2167
+ bucketId: store$1.bucketId,
2168
+ bucketArn: store$1.bucketArn,
2169
+ bucketRegionalDomainName: store$1.bucketRegionalDomainName
2185
2170
  }] : []
2186
2171
  ).concat(declaredServedBuckets);
2187
2172
  let match$4 = Plugin_Stack$ReventlessAws.makeUiBundleDistribution("host-ui", Stdlib_Option.getOr(hostUiBundle.bundleVersion, version), Stdlib_Option.getOr(hostUiBundle.assetsDir, Util_Bundle$ReventlessAws.resolvePackageRoot(true, "@reventlessdev/reventless-host-shell") + "/dist"), true, undefined, true, [
@@ -2194,15 +2179,6 @@ function Make($star) {
2194
2179
  let domainEventsEndpointOutput = domainEventsApiOpt !== undefined ? AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt).apply(ep => ep + "/event") : Pulumi.output(undefined);
2195
2180
  let index = hostUiBundle.geocoderPlaceIndex;
2196
2181
  let geocoderEndpointOutput = index !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(index.indexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
2197
- let store$1 = hostUiBundle.uploadBucket;
2198
- let uploadEndpointOutput = store$1 !== undefined ? Upload_Presign_S3$ReventlessAws.make(store$1.bucketName, undefined, undefined, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
2199
- let storeUploadEndpointsOutput = Pulumi.all(declaredStoreServices.map(param => {
2200
- let qualified = param[0];
2201
- return param[4].apply(u => [
2202
- qualified,
2203
- u
2204
- ]);
2205
- }));
2206
2182
  let configJsonContent = Pulumi.all([
2207
2183
  Pulumi.all([
2208
2184
  resolvedDomainApiEndpoint,
@@ -2211,26 +2187,19 @@ function Make($star) {
2211
2187
  cognitoPool.clientId
2212
2188
  ]),
2213
2189
  domainEventsEndpointOutput,
2214
- geocoderEndpointOutput,
2215
- Pulumi.all([
2216
- uploadEndpointOutput,
2217
- storeUploadEndpointsOutput
2218
- ])
2190
+ geocoderEndpointOutput
2219
2191
  ]).apply(param => {
2220
- let match = param[3];
2221
- let storeUploadEps = match[1];
2222
- let uploadEpOpt = match[0];
2223
2192
  let geocoderEpOpt = param[2];
2224
2193
  let eventsEpOpt = param[1];
2225
- let match$1 = param[0];
2194
+ let match = param[0];
2226
2195
  let fields = [
2227
2196
  [
2228
2197
  "apiEndpoint",
2229
- match$1[0]
2198
+ match[0]
2230
2199
  ],
2231
2200
  [
2232
2201
  "platformApiEndpoint",
2233
- match$1[1]
2202
+ match[1]
2234
2203
  ],
2235
2204
  [
2236
2205
  "region",
@@ -2242,11 +2211,11 @@ function Make($star) {
2242
2211
  ],
2243
2212
  [
2244
2213
  "cognitoUserPoolId",
2245
- match$1[2]
2214
+ match[2]
2246
2215
  ],
2247
2216
  [
2248
2217
  "cognitoClientId",
2249
- match$1[3]
2218
+ match[3]
2250
2219
  ],
2251
2220
  [
2252
2221
  "liveUpdates",
@@ -2271,18 +2240,7 @@ function Make($star) {
2271
2240
  "geocoderEndpoint",
2272
2241
  geocoderEpOpt
2273
2242
  ]]) : withEvents;
2274
- let withUpload = uploadEpOpt !== undefined ? withGeocoder.concat([[
2275
- "uploadEndpoint",
2276
- uploadEpOpt
2277
- ]]) : withGeocoder;
2278
- let withStoreUploads = storeUploadEps.length !== 0 ? withUpload.concat([[
2279
- "uploadEndpoints",
2280
- Object.fromEntries(storeUploadEps.map(param => [
2281
- param[0],
2282
- param[1]
2283
- ]))
2284
- ]]) : withUpload;
2285
- return JSON.stringify(Object.fromEntries(withStoreUploads));
2243
+ return JSON.stringify(Object.fromEntries(withGeocoder));
2286
2244
  });
2287
2245
  new (Aws.s3.BucketObject)("host-ui-config-json", {
2288
2246
  bucket: bucketName,
@@ -60,14 +60,16 @@ type response = {
60
60
  body: string,
61
61
  }
62
62
 
63
- // Permissive CORS so the browser demo can call the endpoint from any origin.
64
- let corsHeaders = () =>
65
- Dict.fromArray([
66
- ("content-type", "application/json"),
67
- ("access-control-allow-origin", "*"),
68
- ("access-control-allow-methods", "GET,OPTIONS"),
69
- ("access-control-allow-headers", "*"),
70
- ])
63
+ // CORS belongs to the Function URL's own `cors` configuration and to nothing
64
+ // else. AWS injects the allow-origin header itself whenever the request carries
65
+ // an `Origin`, so a handler that also sets one sends the header *twice* — and a
66
+ // browser rejects `Access-Control-Allow-Origin: *, *` outright, failing every
67
+ // cross-origin call while leaving `curl` (which sends no `Origin`, so AWS adds
68
+ // nothing) working perfectly.
69
+ //
70
+ // It is also the only way `~corsOrigins` can mean anything: a hardcoded `*`
71
+ // here would keep answering `*` for a deployment that narrowed the allow-list.
72
+ let jsonHeaders = () => Dict.fromArray([("content-type", "application/json")])
71
73
 
72
74
  // Pull `q` from the parsed query-string params, falling back to the raw string.
73
75
  let readQueryParam = (event: functionUrlEvent): option<string> =>
@@ -93,7 +95,7 @@ let handler = async (event: functionUrlEvent): response => {
93
95
  let indexName = getEnv("PLACE_INDEX_NAME")->Option.getOr("")
94
96
  let q = readQueryParam(event)->Option.getOr("")
95
97
  if indexName == "" || q == "" {
96
- {statusCode: 200, headers: corsHeaders(), body: "[]"}
98
+ {statusCode: 200, headers: jsonHeaders(), body: "[]"}
97
99
  } else {
98
100
  let client = makeLocationClient()
99
101
  let resp = await client->send(
@@ -124,13 +126,13 @@ let handler = async (event: functionUrlEvent): response => {
124
126
  )
125
127
  {
126
128
  statusCode: 200,
127
- headers: corsHeaders(),
129
+ headers: jsonHeaders(),
128
130
  body: results->JSON.Encode.array->JSON.stringify,
129
131
  }
130
132
  }
131
133
  } catch {
132
134
  | exn =>
133
135
  Console.error2("Geocoder: search failed", exn)
134
- {statusCode: 200, headers: corsHeaders(), body: "[]"}
136
+ {statusCode: 200, headers: jsonHeaders(), body: "[]"}
135
137
  }
136
138
  }