@reventlessdev/reventless-aws 3.0.0-alpha.239 → 3.0.0-alpha.241

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.
@@ -36,6 +36,7 @@ import * as PluginBehavior$ReventlessCore from "@reventlessdev/reventless-core/s
36
36
  import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
37
37
  import * as PgProjectionFeed$ReventlessAws from "./adapter/Postgres/PgProjectionFeed.res.mjs";
38
38
  import * as Util_LocalConfig$ReventlessAws from "./util/Util_LocalConfig.res.mjs";
39
+ import * as Util_StoreLayout$ReventlessAws from "./util/Util_StoreLayout.res.mjs";
39
40
  import * as IndexJs from "@pulumi/pulumi/runtime/index.js";
40
41
  import * as AppSync_EventsApi$ReventlessAws from "./adapter/Api/AppSync_EventsApi.res.mjs";
41
42
  import * as AppSync_MergedApi$ReventlessAws from "./components/Api/AppSync_MergedApi.res.mjs";
@@ -68,6 +69,7 @@ import * as UiFragments_Projection$ReventlessCore from "@reventlessdev/reventles
68
69
  import * as Aggregate_Builder_Single$ReventlessAws from "./components/Aggregate_Builder_Single.res.mjs";
69
70
  import * as ReadModel_Builder_Single$ReventlessAws from "./components/ReadModel_Builder_Single.res.mjs";
70
71
  import * as StateChangeSlice_Builder$ReventlessAws from "./components/StateChangeSlice_Builder.res.mjs";
72
+ import * as Capability_ObjectStore_S3$ReventlessAws from "./capability/Capability_ObjectStore_S3.res.mjs";
71
73
  import * as EventCollectorChannel_SQS$ReventlessAws from "./adapter/EventCollector/EventCollectorChannel_SQS.res.mjs";
72
74
  import * as PgChangeFeedRelay_Builder$ReventlessAws from "./adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs";
73
75
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "./adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs";
@@ -114,6 +116,14 @@ function getSplitApiOutputs() {
114
116
  return splitApiOutputsRef.contents;
115
117
  }
116
118
 
119
+ let objectStoreEndpointsRef = {
120
+ contents: []
121
+ };
122
+
123
+ function getObjectStoreEndpoints() {
124
+ return objectStoreEndpointsRef.contents;
125
+ }
126
+
117
127
  function MakeWithConfig(Config) {
118
128
  Stdlib_Option.forEach(Config.commandHandlerConfig.aggregates, param => {
119
129
  Stdlib_Option.forEach(param.sync, AggregateRuntime_Builder_Single$ReventlessAws.setConfig);
@@ -708,7 +718,8 @@ function MakeWithConfig(Config) {
708
718
  log.info("Platform", undefined, `v` + version);
709
719
  return Pervasives.failwith("makePlatform is not supported on AWS — deploy the platform with deployPlatform and each plugin with deployPlugin (merged-API composition).");
710
720
  };
711
- let deployPlatform = (version, hostUiBundle) => {
721
+ let deployPlatform = (version, hostUiBundle, capabilitiesOpt) => {
722
+ let capabilities = capabilitiesOpt !== undefined ? capabilitiesOpt : [];
712
723
  log.info("Platform:deployPlatform", undefined, `v` + version);
713
724
  let scheduler = makeScheduler();
714
725
  hooks_scheduler.contents = scheduler;
@@ -868,41 +879,179 @@ function MakeWithConfig(Config) {
868
879
  adminResources: param[4]
869
880
  });
870
881
  });
882
+ let stackName = Pulumi.getStack();
883
+ let storeLayout = Util_StoreLayout$ReventlessAws.layoutFor(stackName, Util_HostUiDomain$ReventlessAws.resolveProdStacks());
884
+ let storeProtection = Util_StoreLayout$ReventlessAws.protectionFor(stackName, undefined);
885
+ let declaredStores = Stdlib_Array.reduce(Stdlib_Array.filterMap(capabilities, c => {
886
+ if (typeof c !== "object") {
887
+ return;
888
+ } else {
889
+ return [
890
+ c.plugin,
891
+ c.store
892
+ ];
893
+ }
894
+ }), [], (acc, param) => {
895
+ let store = param[1];
896
+ let plugin = param[0];
897
+ if (acc.some(param => param[0] === plugin ? param[1] === store : false)) {
898
+ return acc;
899
+ } else {
900
+ return acc.concat([[
901
+ plugin,
902
+ store
903
+ ]]);
904
+ }
905
+ });
906
+ if (declaredStores.length !== 0) {
907
+ let tmp;
908
+ tmp = storeLayout === "PerStore" ? "bucket per store" : "shared bucket, {store}/… prefixes";
909
+ let tmp$1;
910
+ tmp$1 = storeProtection === "Protected" ? "protected" : "unprotected (disposable stack)";
911
+ log.info("Platform:deployPlatform", undefined, `object stores: ` + tmp + `, ` + tmp$1 + ` — stack ` + stackName);
912
+ }
913
+ let storeBuckets = {};
914
+ let storeBucketOrder = [];
915
+ let declaredStoreServices = declaredStores.map(param => {
916
+ let store = param[1];
917
+ let plugin = param[0];
918
+ let bucketName = Util_StoreLayout$ReventlessAws.bucketNameFor(storeLayout, stackName, plugin, store);
919
+ let keyPrefix = Util_StoreLayout$ReventlessAws.keyPrefixFor(store);
920
+ let b = storeBuckets[bucketName];
921
+ let bucket;
922
+ if (b !== undefined) {
923
+ bucket = b;
924
+ } else {
925
+ let tmp;
926
+ tmp = storeLayout === "PerStore" ? plugin : undefined;
927
+ let b$1 = Capability_ObjectStore_S3$ReventlessAws.make(bucketName, keyPrefix, tmp, storeProtection === "Protected", storeProtection === "Unprotected", undefined, undefined);
928
+ storeBuckets[bucketName] = b$1;
929
+ storeBucketOrder.push(bucketName);
930
+ bucket = b$1;
931
+ }
932
+ let storeHandle = Capability_ObjectStore_S3$ReventlessAws.underPrefix(bucket, keyPrefix);
933
+ let presign = Upload_Presign_S3$ReventlessAws.make(storeHandle.bucketName, undefined, storeHandle.keyPrefix, `UploadPresign-` + plugin + `-` + store, undefined);
934
+ return [
935
+ plugin + `.` + store,
936
+ keyPrefix,
937
+ bucketName,
938
+ presign.url
939
+ ];
940
+ });
941
+ let declaredServedBuckets = Stdlib_Array.filterMap(storeBucketOrder, bucketName => Stdlib_Option.map(storeBuckets[bucketName], b => ({
942
+ id: bucketName,
943
+ prefixes: Stdlib_Array.filterMap(declaredStoreServices, param => {
944
+ if (param[2] === bucketName) {
945
+ return param[1];
946
+ }
947
+ }),
948
+ bucketId: b.bucketId,
949
+ bucketArn: b.bucketArn,
950
+ bucketRegionalDomainName: b.bucketRegionalDomainName
951
+ })));
952
+ let match$1 = Util_StoreLayout$ReventlessAws.servingFor(Stdlib_Option.isSome(hostUiBundle), declaredServedBuckets.length);
953
+ let storeServingBaseUrl;
954
+ switch (match$1) {
955
+ case "NoStores" :
956
+ case "HostShell" :
957
+ storeServingBaseUrl = undefined;
958
+ break;
959
+ case "PlatformOwned" :
960
+ storeServingBaseUrl = Plugin_Stack$ReventlessAws.makeServedBucketDistribution("object-stores", declaredServedBuckets, undefined);
961
+ break;
962
+ }
963
+ let declaredStoreEndpoints = declaredStoreServices.map(param => ({
964
+ store: param[0],
965
+ keyPrefix: param[1],
966
+ bucketName: param[2],
967
+ uploadUrl: param[3],
968
+ baseUrl: storeServingBaseUrl
969
+ }));
970
+ objectStoreEndpointsRef.contents = declaredStoreEndpoints;
971
+ if (declaredStoreEndpoints.length !== 0) {
972
+ Pulumi$Pulumi.$$export("uploadEndpoints", Pulumi.all(declaredStoreEndpoints.map(e => e.uploadUrl.apply(u => [
973
+ e.store,
974
+ u
975
+ ]))).apply(pairs => Object.fromEntries(pairs)));
976
+ Pulumi$Pulumi.$$export("objectStores", Pulumi.all(declaredStoreEndpoints.map(e => {
977
+ let u = e.baseUrl;
978
+ if (u !== undefined) {
979
+ return u.apply(b => [
980
+ e,
981
+ b
982
+ ]);
983
+ } else {
984
+ return Pulumi.output([
985
+ e,
986
+ undefined
987
+ ]);
988
+ }
989
+ })).apply(pairs => Object.fromEntries(pairs.map(param => {
990
+ let baseUrl = param[1];
991
+ let e = param[0];
992
+ return [
993
+ e.store,
994
+ Object.fromEntries([
995
+ [
996
+ "bucketName",
997
+ e.bucketName
998
+ ],
999
+ [
1000
+ "keyPrefix",
1001
+ e.keyPrefix
1002
+ ]
1003
+ ].concat(baseUrl !== undefined ? [[
1004
+ "baseUrl",
1005
+ baseUrl
1006
+ ]] : []))
1007
+ ];
1008
+ }))));
1009
+ }
871
1010
  if (hostUiBundle !== undefined) {
872
- let match$1 = Util_LocalConfig$ReventlessAws.get("hostUiBaseDomain");
873
- let match$2 = Util_LocalConfig$ReventlessAws.get("hostUiHostedZoneId");
1011
+ let match$2 = Util_LocalConfig$ReventlessAws.get("hostUiBaseDomain");
1012
+ let match$3 = Util_LocalConfig$ReventlessAws.get("hostUiHostedZoneId");
874
1013
  let customDomain;
875
- if (match$1 !== undefined && match$2 !== undefined) {
1014
+ if (match$2 !== undefined && match$3 !== undefined) {
876
1015
  let stack = Pulumi.getStack();
877
1016
  let baseName = Stdlib_Option.getOr(Util_LocalConfig$ReventlessAws.get("hostUiBaseName"), Pulumi.getProject());
878
1017
  let prodStacks = Stdlib_Option.getOr(Stdlib_Option.map(Util_LocalConfig$ReventlessAws.get("hostUiProdStacks"), Util_HostUiDomain$ReventlessAws.parseProdStacks), Util_HostUiDomain$ReventlessAws.defaultProdStacks);
879
- let fqdn = Util_HostUiDomain$ReventlessAws.deriveFqdn(baseName, stack, match$1, prodStacks);
1018
+ let fqdn = Util_HostUiDomain$ReventlessAws.deriveFqdn(baseName, stack, match$2, prodStacks);
880
1019
  customDomain = {
881
1020
  fqdn: fqdn,
882
- hostedZoneId: match$2
1021
+ hostedZoneId: match$3
883
1022
  };
884
1023
  } else {
885
1024
  customDomain = undefined;
886
1025
  }
887
1026
  let store = hostUiBundle.uploadBucket;
888
- let servedBuckets = store !== undefined ? [{
889
- prefix: Upload_Presign_S3$ReventlessAws.defaultServedPrefix,
890
- bucketId: store.bucketId,
891
- bucketArn: store.bucketArn,
892
- bucketRegionalDomainName: store.bucketRegionalDomainName
893
- }] : [];
894
- let match$3 = Plugin_Stack$ReventlessAws.makeUiBundleDistribution("host-ui", Stdlib_Option.getOr(hostUiBundle.bundleVersion, version), Stdlib_Option.getOr(hostUiBundle.assetsDir, Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-host-shell") + "/dist"), true, undefined, true, [
1027
+ let servedBuckets = (
1028
+ store !== undefined ? [{
1029
+ id: store.keyPrefix,
1030
+ prefixes: [store.keyPrefix],
1031
+ bucketId: store.bucketId,
1032
+ bucketArn: store.bucketArn,
1033
+ bucketRegionalDomainName: store.bucketRegionalDomainName
1034
+ }] : []
1035
+ ).concat(declaredServedBuckets);
1036
+ let match$4 = Plugin_Stack$ReventlessAws.makeUiBundleDistribution("host-ui", Stdlib_Option.getOr(hostUiBundle.bundleVersion, version), Stdlib_Option.getOr(hostUiBundle.assetsDir, Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-host-shell") + "/dist"), true, undefined, true, [
895
1037
  "config.json",
896
1038
  "ui-hints.json"
897
1039
  ], customDomain, servedBuckets);
898
- let bucketName = match$3.bucketName;
1040
+ let bucketName = match$4.bucketName;
899
1041
  let regionStr = Stdlib_Option.getOr(new Pulumi.Config("aws").get("region"), "unknown");
900
1042
  let cognitoPool = Platform_Stack$ReventlessAws.resolveCognitoUserPool();
901
1043
  let domainEventsEndpointOutput = domainEventsApiOpt !== undefined ? AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt).apply(ep => ep + "/event") : Pulumi.output(undefined);
902
1044
  let index = hostUiBundle.geocoderPlaceIndex;
903
1045
  let geocoderEndpointOutput = index !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(index.indexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
904
1046
  let store$1 = hostUiBundle.uploadBucket;
905
- let uploadEndpointOutput = store$1 !== undefined ? Upload_Presign_S3$ReventlessAws.make(store$1.bucketName, undefined, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
1047
+ let uploadEndpointOutput = store$1 !== undefined ? Upload_Presign_S3$ReventlessAws.make(store$1.bucketName, undefined, undefined, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
1048
+ let storeUploadEndpointsOutput = Pulumi.all(declaredStoreServices.map(param => {
1049
+ let qualified = param[0];
1050
+ return param[3].apply(u => [
1051
+ qualified,
1052
+ u
1053
+ ]);
1054
+ }));
906
1055
  let configJsonContent = Pulumi.all([
907
1056
  Pulumi.all([
908
1057
  resolvedDomainApiEndpoint,
@@ -912,20 +1061,25 @@ function MakeWithConfig(Config) {
912
1061
  ]),
913
1062
  domainEventsEndpointOutput,
914
1063
  geocoderEndpointOutput,
915
- uploadEndpointOutput
1064
+ Pulumi.all([
1065
+ uploadEndpointOutput,
1066
+ storeUploadEndpointsOutput
1067
+ ])
916
1068
  ]).apply(param => {
917
- let uploadEpOpt = param[3];
1069
+ let match = param[3];
1070
+ let storeUploadEps = match[1];
1071
+ let uploadEpOpt = match[0];
918
1072
  let geocoderEpOpt = param[2];
919
1073
  let eventsEpOpt = param[1];
920
- let match = param[0];
1074
+ let match$1 = param[0];
921
1075
  let fields = [
922
1076
  [
923
1077
  "apiEndpoint",
924
- match[0]
1078
+ match$1[0]
925
1079
  ],
926
1080
  [
927
1081
  "platformApiEndpoint",
928
- match[1]
1082
+ match$1[1]
929
1083
  ],
930
1084
  [
931
1085
  "region",
@@ -937,11 +1091,11 @@ function MakeWithConfig(Config) {
937
1091
  ],
938
1092
  [
939
1093
  "cognitoUserPoolId",
940
- match[2]
1094
+ match$1[2]
941
1095
  ],
942
1096
  [
943
1097
  "cognitoClientId",
944
- match[3]
1098
+ match$1[3]
945
1099
  ],
946
1100
  [
947
1101
  "liveUpdates",
@@ -970,7 +1124,14 @@ function MakeWithConfig(Config) {
970
1124
  "uploadEndpoint",
971
1125
  uploadEpOpt
972
1126
  ]]) : withGeocoder;
973
- return JSON.stringify(Object.fromEntries(withUpload));
1127
+ let withStoreUploads = storeUploadEps.length !== 0 ? withUpload.concat([[
1128
+ "uploadEndpoints",
1129
+ Object.fromEntries(storeUploadEps.map(param => [
1130
+ param[0],
1131
+ param[1]
1132
+ ]))
1133
+ ]]) : withUpload;
1134
+ return JSON.stringify(Object.fromEntries(withStoreUploads));
974
1135
  });
975
1136
  new (Aws.s3.BucketObject)("host-ui-config-json", {
976
1137
  bucket: bucketName,
@@ -988,7 +1149,7 @@ function MakeWithConfig(Config) {
988
1149
  contentType: "application/json"
989
1150
  });
990
1151
  }
991
- Pulumi$Pulumi.$$export("hostShellUrl", match$3.distributionUrl);
1152
+ Pulumi$Pulumi.$$export("hostShellUrl", match$4.distributionUrl);
992
1153
  }
993
1154
  return Pulumi$Pulumi.getOutputs();
994
1155
  };
@@ -1724,7 +1885,8 @@ function Make($star) {
1724
1885
  log.info("Platform", undefined, `v` + version);
1725
1886
  return Pervasives.failwith("makePlatform is not supported on AWS — deploy the platform with deployPlatform and each plugin with deployPlugin (merged-API composition).");
1726
1887
  };
1727
- let deployPlatform = (version, hostUiBundle) => {
1888
+ let deployPlatform = (version, hostUiBundle, capabilitiesOpt) => {
1889
+ let capabilities = capabilitiesOpt !== undefined ? capabilitiesOpt : [];
1728
1890
  log.info("Platform:deployPlatform", undefined, `v` + version);
1729
1891
  let scheduler = makeScheduler();
1730
1892
  hooks_scheduler.contents = scheduler;
@@ -1861,41 +2023,179 @@ function Make($star) {
1861
2023
  adminResources: param[4]
1862
2024
  });
1863
2025
  });
2026
+ let stackName = Pulumi.getStack();
2027
+ let storeLayout = Util_StoreLayout$ReventlessAws.layoutFor(stackName, Util_HostUiDomain$ReventlessAws.resolveProdStacks());
2028
+ let storeProtection = Util_StoreLayout$ReventlessAws.protectionFor(stackName, undefined);
2029
+ let declaredStores = Stdlib_Array.reduce(Stdlib_Array.filterMap(capabilities, c => {
2030
+ if (typeof c !== "object") {
2031
+ return;
2032
+ } else {
2033
+ return [
2034
+ c.plugin,
2035
+ c.store
2036
+ ];
2037
+ }
2038
+ }), [], (acc, param) => {
2039
+ let store = param[1];
2040
+ let plugin = param[0];
2041
+ if (acc.some(param => param[0] === plugin ? param[1] === store : false)) {
2042
+ return acc;
2043
+ } else {
2044
+ return acc.concat([[
2045
+ plugin,
2046
+ store
2047
+ ]]);
2048
+ }
2049
+ });
2050
+ if (declaredStores.length !== 0) {
2051
+ let tmp;
2052
+ tmp = storeLayout === "PerStore" ? "bucket per store" : "shared bucket, {store}/… prefixes";
2053
+ let tmp$1;
2054
+ tmp$1 = storeProtection === "Protected" ? "protected" : "unprotected (disposable stack)";
2055
+ log.info("Platform:deployPlatform", undefined, `object stores: ` + tmp + `, ` + tmp$1 + ` — stack ` + stackName);
2056
+ }
2057
+ let storeBuckets = {};
2058
+ let storeBucketOrder = [];
2059
+ let declaredStoreServices = declaredStores.map(param => {
2060
+ let store = param[1];
2061
+ let plugin = param[0];
2062
+ let bucketName = Util_StoreLayout$ReventlessAws.bucketNameFor(storeLayout, stackName, plugin, store);
2063
+ let keyPrefix = Util_StoreLayout$ReventlessAws.keyPrefixFor(store);
2064
+ let b = storeBuckets[bucketName];
2065
+ let bucket;
2066
+ if (b !== undefined) {
2067
+ bucket = b;
2068
+ } else {
2069
+ let tmp;
2070
+ tmp = storeLayout === "PerStore" ? plugin : undefined;
2071
+ let b$1 = Capability_ObjectStore_S3$ReventlessAws.make(bucketName, keyPrefix, tmp, storeProtection === "Protected", storeProtection === "Unprotected", undefined, undefined);
2072
+ storeBuckets[bucketName] = b$1;
2073
+ storeBucketOrder.push(bucketName);
2074
+ bucket = b$1;
2075
+ }
2076
+ let storeHandle = Capability_ObjectStore_S3$ReventlessAws.underPrefix(bucket, keyPrefix);
2077
+ let presign = Upload_Presign_S3$ReventlessAws.make(storeHandle.bucketName, undefined, storeHandle.keyPrefix, `UploadPresign-` + plugin + `-` + store, undefined);
2078
+ return [
2079
+ plugin + `.` + store,
2080
+ keyPrefix,
2081
+ bucketName,
2082
+ presign.url
2083
+ ];
2084
+ });
2085
+ let declaredServedBuckets = Stdlib_Array.filterMap(storeBucketOrder, bucketName => Stdlib_Option.map(storeBuckets[bucketName], b => ({
2086
+ id: bucketName,
2087
+ prefixes: Stdlib_Array.filterMap(declaredStoreServices, param => {
2088
+ if (param[2] === bucketName) {
2089
+ return param[1];
2090
+ }
2091
+ }),
2092
+ bucketId: b.bucketId,
2093
+ bucketArn: b.bucketArn,
2094
+ bucketRegionalDomainName: b.bucketRegionalDomainName
2095
+ })));
2096
+ let match$1 = Util_StoreLayout$ReventlessAws.servingFor(Stdlib_Option.isSome(hostUiBundle), declaredServedBuckets.length);
2097
+ let storeServingBaseUrl;
2098
+ switch (match$1) {
2099
+ case "NoStores" :
2100
+ case "HostShell" :
2101
+ storeServingBaseUrl = undefined;
2102
+ break;
2103
+ case "PlatformOwned" :
2104
+ storeServingBaseUrl = Plugin_Stack$ReventlessAws.makeServedBucketDistribution("object-stores", declaredServedBuckets, undefined);
2105
+ break;
2106
+ }
2107
+ let declaredStoreEndpoints = declaredStoreServices.map(param => ({
2108
+ store: param[0],
2109
+ keyPrefix: param[1],
2110
+ bucketName: param[2],
2111
+ uploadUrl: param[3],
2112
+ baseUrl: storeServingBaseUrl
2113
+ }));
2114
+ objectStoreEndpointsRef.contents = declaredStoreEndpoints;
2115
+ if (declaredStoreEndpoints.length !== 0) {
2116
+ Pulumi$Pulumi.$$export("uploadEndpoints", Pulumi.all(declaredStoreEndpoints.map(e => e.uploadUrl.apply(u => [
2117
+ e.store,
2118
+ u
2119
+ ]))).apply(pairs => Object.fromEntries(pairs)));
2120
+ Pulumi$Pulumi.$$export("objectStores", Pulumi.all(declaredStoreEndpoints.map(e => {
2121
+ let u = e.baseUrl;
2122
+ if (u !== undefined) {
2123
+ return u.apply(b => [
2124
+ e,
2125
+ b
2126
+ ]);
2127
+ } else {
2128
+ return Pulumi.output([
2129
+ e,
2130
+ undefined
2131
+ ]);
2132
+ }
2133
+ })).apply(pairs => Object.fromEntries(pairs.map(param => {
2134
+ let baseUrl = param[1];
2135
+ let e = param[0];
2136
+ return [
2137
+ e.store,
2138
+ Object.fromEntries([
2139
+ [
2140
+ "bucketName",
2141
+ e.bucketName
2142
+ ],
2143
+ [
2144
+ "keyPrefix",
2145
+ e.keyPrefix
2146
+ ]
2147
+ ].concat(baseUrl !== undefined ? [[
2148
+ "baseUrl",
2149
+ baseUrl
2150
+ ]] : []))
2151
+ ];
2152
+ }))));
2153
+ }
1864
2154
  if (hostUiBundle !== undefined) {
1865
- let match$1 = Util_LocalConfig$ReventlessAws.get("hostUiBaseDomain");
1866
- let match$2 = Util_LocalConfig$ReventlessAws.get("hostUiHostedZoneId");
2155
+ let match$2 = Util_LocalConfig$ReventlessAws.get("hostUiBaseDomain");
2156
+ let match$3 = Util_LocalConfig$ReventlessAws.get("hostUiHostedZoneId");
1867
2157
  let customDomain;
1868
- if (match$1 !== undefined && match$2 !== undefined) {
2158
+ if (match$2 !== undefined && match$3 !== undefined) {
1869
2159
  let stack = Pulumi.getStack();
1870
2160
  let baseName = Stdlib_Option.getOr(Util_LocalConfig$ReventlessAws.get("hostUiBaseName"), Pulumi.getProject());
1871
2161
  let prodStacks = Stdlib_Option.getOr(Stdlib_Option.map(Util_LocalConfig$ReventlessAws.get("hostUiProdStacks"), Util_HostUiDomain$ReventlessAws.parseProdStacks), Util_HostUiDomain$ReventlessAws.defaultProdStacks);
1872
- let fqdn = Util_HostUiDomain$ReventlessAws.deriveFqdn(baseName, stack, match$1, prodStacks);
2162
+ let fqdn = Util_HostUiDomain$ReventlessAws.deriveFqdn(baseName, stack, match$2, prodStacks);
1873
2163
  customDomain = {
1874
2164
  fqdn: fqdn,
1875
- hostedZoneId: match$2
2165
+ hostedZoneId: match$3
1876
2166
  };
1877
2167
  } else {
1878
2168
  customDomain = undefined;
1879
2169
  }
1880
2170
  let store = hostUiBundle.uploadBucket;
1881
- let servedBuckets = store !== undefined ? [{
1882
- prefix: Upload_Presign_S3$ReventlessAws.defaultServedPrefix,
1883
- bucketId: store.bucketId,
1884
- bucketArn: store.bucketArn,
1885
- bucketRegionalDomainName: store.bucketRegionalDomainName
1886
- }] : [];
1887
- let match$3 = Plugin_Stack$ReventlessAws.makeUiBundleDistribution("host-ui", Stdlib_Option.getOr(hostUiBundle.bundleVersion, version), Stdlib_Option.getOr(hostUiBundle.assetsDir, Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-host-shell") + "/dist"), true, undefined, true, [
2171
+ let servedBuckets = (
2172
+ store !== undefined ? [{
2173
+ id: store.keyPrefix,
2174
+ prefixes: [store.keyPrefix],
2175
+ bucketId: store.bucketId,
2176
+ bucketArn: store.bucketArn,
2177
+ bucketRegionalDomainName: store.bucketRegionalDomainName
2178
+ }] : []
2179
+ ).concat(declaredServedBuckets);
2180
+ let match$4 = Plugin_Stack$ReventlessAws.makeUiBundleDistribution("host-ui", Stdlib_Option.getOr(hostUiBundle.bundleVersion, version), Stdlib_Option.getOr(hostUiBundle.assetsDir, Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-host-shell") + "/dist"), true, undefined, true, [
1888
2181
  "config.json",
1889
2182
  "ui-hints.json"
1890
2183
  ], customDomain, servedBuckets);
1891
- let bucketName = match$3.bucketName;
2184
+ let bucketName = match$4.bucketName;
1892
2185
  let regionStr = Stdlib_Option.getOr(new Pulumi.Config("aws").get("region"), "unknown");
1893
2186
  let cognitoPool = Platform_Stack$ReventlessAws.resolveCognitoUserPool();
1894
2187
  let domainEventsEndpointOutput = domainEventsApiOpt !== undefined ? AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt).apply(ep => ep + "/event") : Pulumi.output(undefined);
1895
2188
  let index = hostUiBundle.geocoderPlaceIndex;
1896
2189
  let geocoderEndpointOutput = index !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(index.indexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
1897
2190
  let store$1 = hostUiBundle.uploadBucket;
1898
- let uploadEndpointOutput = store$1 !== undefined ? Upload_Presign_S3$ReventlessAws.make(store$1.bucketName, undefined, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
2191
+ let uploadEndpointOutput = store$1 !== undefined ? Upload_Presign_S3$ReventlessAws.make(store$1.bucketName, undefined, undefined, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
2192
+ let storeUploadEndpointsOutput = Pulumi.all(declaredStoreServices.map(param => {
2193
+ let qualified = param[0];
2194
+ return param[3].apply(u => [
2195
+ qualified,
2196
+ u
2197
+ ]);
2198
+ }));
1899
2199
  let configJsonContent = Pulumi.all([
1900
2200
  Pulumi.all([
1901
2201
  resolvedDomainApiEndpoint,
@@ -1905,20 +2205,25 @@ function Make($star) {
1905
2205
  ]),
1906
2206
  domainEventsEndpointOutput,
1907
2207
  geocoderEndpointOutput,
1908
- uploadEndpointOutput
2208
+ Pulumi.all([
2209
+ uploadEndpointOutput,
2210
+ storeUploadEndpointsOutput
2211
+ ])
1909
2212
  ]).apply(param => {
1910
- let uploadEpOpt = param[3];
2213
+ let match = param[3];
2214
+ let storeUploadEps = match[1];
2215
+ let uploadEpOpt = match[0];
1911
2216
  let geocoderEpOpt = param[2];
1912
2217
  let eventsEpOpt = param[1];
1913
- let match = param[0];
2218
+ let match$1 = param[0];
1914
2219
  let fields = [
1915
2220
  [
1916
2221
  "apiEndpoint",
1917
- match[0]
2222
+ match$1[0]
1918
2223
  ],
1919
2224
  [
1920
2225
  "platformApiEndpoint",
1921
- match[1]
2226
+ match$1[1]
1922
2227
  ],
1923
2228
  [
1924
2229
  "region",
@@ -1930,11 +2235,11 @@ function Make($star) {
1930
2235
  ],
1931
2236
  [
1932
2237
  "cognitoUserPoolId",
1933
- match[2]
2238
+ match$1[2]
1934
2239
  ],
1935
2240
  [
1936
2241
  "cognitoClientId",
1937
- match[3]
2242
+ match$1[3]
1938
2243
  ],
1939
2244
  [
1940
2245
  "liveUpdates",
@@ -1963,7 +2268,14 @@ function Make($star) {
1963
2268
  "uploadEndpoint",
1964
2269
  uploadEpOpt
1965
2270
  ]]) : withGeocoder;
1966
- return JSON.stringify(Object.fromEntries(withUpload));
2271
+ let withStoreUploads = storeUploadEps.length !== 0 ? withUpload.concat([[
2272
+ "uploadEndpoints",
2273
+ Object.fromEntries(storeUploadEps.map(param => [
2274
+ param[0],
2275
+ param[1]
2276
+ ]))
2277
+ ]]) : withUpload;
2278
+ return JSON.stringify(Object.fromEntries(withStoreUploads));
1967
2279
  });
1968
2280
  new (Aws.s3.BucketObject)("host-ui-config-json", {
1969
2281
  bucket: bucketName,
@@ -1981,7 +2293,7 @@ function Make($star) {
1981
2293
  contentType: "application/json"
1982
2294
  });
1983
2295
  }
1984
- Pulumi$Pulumi.$$export("hostShellUrl", match$3.distributionUrl);
2296
+ Pulumi$Pulumi.$$export("hostShellUrl", match$4.distributionUrl);
1985
2297
  }
1986
2298
  return Pulumi$Pulumi.getOutputs();
1987
2299
  };
@@ -2133,6 +2445,8 @@ export {
2133
2445
  getApiConfig,
2134
2446
  splitApiOutputsRef,
2135
2447
  getSplitApiOutputs,
2448
+ objectStoreEndpointsRef,
2449
+ getObjectStoreEndpoints,
2136
2450
  MakeWithConfig,
2137
2451
  Make,
2138
2452
  }
@@ -42,9 +42,19 @@ let make = (
42
42
  // bucket's CloudFront `{prefix}/*` behavior so the returned `/{key}` ref
43
43
  // resolves.
44
44
  ~servedPrefix: string=defaultServedPrefix,
45
+ // Resource-name stem. Defaults to the single-service name this adapter had
46
+ // when a deployment could only have one store, so the existing service keeps
47
+ // its identity; declared stores each pass their own, because there is one
48
+ // service per store.
49
+ //
50
+ // One service per store rather than one service with wildcard write across
51
+ // stores: a single wide-scoped presigner reintroduces exactly the blast radius
52
+ // the per-store split exists to remove, and it does so invisibly — every
53
+ // upload still works.
54
+ ~name: string="UploadPresignService",
45
55
  ~opts=?,
46
56
  ): serviceOutputs => {
47
- let serviceName = "UploadPresignService"
57
+ let serviceName = name
48
58
  let opts =
49
59
  opts->Option.map(ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions)
50
60
 
@@ -61,12 +71,18 @@ let make = (
61
71
  )
62
72
 
63
73
  // CloudWatch Logs (so failures are observable) plus least-privilege
64
- // `s3:PutObject` on any key under the target bucket.
74
+ // `s3:PutObject` on this store's own keys.
75
+ //
76
+ // Scoped to `{bucket}/{servedPrefix}/*`, not to the whole bucket. Because keys
77
+ // are rooted at the store's prefix in *both* layouts, this one expression is
78
+ // least-privilege in both: a dedicated bucket's service still cannot write
79
+ // outside its prefix, and a shared bucket's services cannot write into each
80
+ // other's stores. The layouts stay one model — even the policy does not fork.
65
81
  let _policy =
66
82
  bucketName
67
83
  ->Pulumi.Output.fromInput
68
84
  ->Pulumi.Output.apply(b => {
69
- let arn = `arn:aws:s3:::${b}/*`
85
+ let arn = `arn:aws:s3:::${b}/${servedPrefix}/*`
70
86
  let _ = IAM.RolePolicy.make(
71
87
  ~name=`${serviceName}Policy`,
72
88
  ~args={
@@ -14,16 +14,16 @@ import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/
14
14
 
15
15
  let defaultServedPrefix = "uploads";
16
16
 
17
- function make(bucketName, corsOriginsOpt, servedPrefixOpt, opts) {
17
+ function make(bucketName, corsOriginsOpt, servedPrefixOpt, nameOpt, opts) {
18
18
  let corsOrigins = corsOriginsOpt !== undefined ? corsOriginsOpt : ["*"];
19
19
  let servedPrefix = servedPrefixOpt !== undefined ? servedPrefixOpt : defaultServedPrefix;
20
- let serviceName = "UploadPresignService";
20
+ let name = nameOpt !== undefined ? nameOpt : "UploadPresignService";
21
21
  let opts$1 = Stdlib_Option.map(opts, Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions);
22
- let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(serviceName, Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(serviceName, "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
22
+ let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name, Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name, "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
23
23
  bucketName.apply(b => {
24
- let arn = `arn:aws:s3:::` + b + `/*`;
25
- new (Aws.iam.RolePolicy)(serviceName + `Policy`, {
26
- policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, serviceName + `Policy`, [
24
+ let arn = `arn:aws:s3:::` + b + `/` + servedPrefix + `/*`;
25
+ new (Aws.iam.RolePolicy)(name + `Policy`, {
26
+ policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `Policy`, [
27
27
  {
28
28
  Sid: "AllowLambdaLogging",
29
29
  Effect: "Allow",
@@ -50,7 +50,7 @@ function make(bucketName, corsOriginsOpt, servedPrefixOpt, opts) {
50
50
  ]]);
51
51
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs", packageDirs, undefined);
52
52
  let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
53
- let lambda = new (Aws.lambda.Function)(serviceName, {
53
+ let lambda = new (Aws.lambda.Function)(name, {
54
54
  handler: "index.handler",
55
55
  runtime: "nodejs22.x",
56
56
  code: match.code,
@@ -58,7 +58,7 @@ function make(bucketName, corsOriginsOpt, servedPrefixOpt, opts) {
58
58
  memorySize: 256,
59
59
  timeout: 30,
60
60
  layers: layers,
61
- tags: AWS_Tags$ReventlessAws.make(serviceName, "Platform", "Runtime", "Platform", undefined, undefined, undefined, undefined),
61
+ tags: AWS_Tags$ReventlessAws.make(name, "Platform", "Runtime", "Platform", undefined, undefined, undefined, undefined),
62
62
  environment: {
63
63
  variables: Object.fromEntries([
64
64
  [
@@ -85,7 +85,7 @@ function make(bucketName, corsOriginsOpt, servedPrefixOpt, opts) {
85
85
  },
86
86
  sourceCodeHash: match.sourceCodeHash
87
87
  }, opts$1 !== undefined ? Primitive_option.valFromOption(opts$1) : undefined);
88
- let functionUrl = new (Aws.lambda.FunctionUrl)(serviceName + `Url`, {
88
+ let functionUrl = new (Aws.lambda.FunctionUrl)(name + `Url`, {
89
89
  authorizationType: "NONE",
90
90
  functionName: lambda.name,
91
91
  cors: {