@reventlessdev/reventless-aws 3.0.0-alpha.236 → 3.0.0-alpha.237
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 +8 -0
- package/package.json +6 -6
- package/src/Platform.res +63 -32
- package/src/Platform.res.mjs +35 -14
- package/src/adapter/Api/AppSync_EventsApi.res +43 -2
- package/src/adapter/Api/AppSync_EventsApi.res.mjs +20 -1
- package/src/adapter/Upload/Upload_Presign_S3.res +12 -2
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +4 -1
- package/src/capability/Capability_Geocoding_AwsLocation.res +51 -0
- package/src/capability/Capability_Geocoding_AwsLocation.res.mjs +37 -0
- package/src/capability/Capability_ObjectStore_S3.res +84 -0
- package/src/capability/Capability_ObjectStore_S3.res.mjs +48 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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.237 (2026-07-28)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **aws:** provision platform capabilities through framework helpers ([5f87c57](https://github.com/ReventlessDev/reventless-core/commit/5f87c57c7c117dccb44c88d6132e5270e8707bc2))
|
|
11
|
+
* **events:** client-publishable /client namespace + local Events transport ([af98ce0](https://github.com/ReventlessDev/reventless-core/commit/af98ce07fb495bc629fcf3dc4f8383a9e0f2890d))
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
# 3.0.0-alpha.236 (2026-07-27)
|
|
7
15
|
|
|
8
16
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.237",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"uuid": "^13.0.0",
|
|
14
14
|
"@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.25",
|
|
15
15
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.31",
|
|
16
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.9",
|
|
17
16
|
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.61",
|
|
18
17
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
|
|
18
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.9",
|
|
19
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.187",
|
|
19
20
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.17",
|
|
20
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
21
|
-
"@reventlessdev/reventless-
|
|
22
|
-
"@reventlessdev/reventless-
|
|
23
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.81",
|
|
21
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.107",
|
|
22
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.51",
|
|
23
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.82",
|
|
24
24
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.29"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
package/src/Platform.res
CHANGED
|
@@ -999,8 +999,12 @@ module MakeWithConfig = (
|
|
|
999
999
|
// `config.json` next to `index.html` so the shell discovers `apiEndpoint` and
|
|
1000
1000
|
// `region` at boot without rebuild.
|
|
1001
1001
|
type hostUiBundleConfig = {
|
|
1002
|
-
|
|
1003
|
-
|
|
1002
|
+
// Directory holding the built shell bundle. Defaults to the resolved
|
|
1003
|
+
// `@reventlessdev/reventless-host-shell` dist — the line every platform
|
|
1004
|
+
// root used to repeat verbatim. Set it only to host a different bundle.
|
|
1005
|
+
assetsDir?: string,
|
|
1006
|
+
// Defaults to the `~version` passed to `deployPlatform`.
|
|
1007
|
+
bundleVersion?: string,
|
|
1004
1008
|
// Optional path to a static AutoUI `ui-hints.json` (assistant-authored,
|
|
1005
1009
|
// plain data — no app rebuild). When set, the deploy reads it and writes it
|
|
1006
1010
|
// verbatim as a `ui-hints.json` BucketObject beside `config.json`, so the
|
|
@@ -1008,23 +1012,26 @@ module MakeWithConfig = (
|
|
|
1008
1012
|
// written; the shell treats the resulting 404 as "no hints" and boots
|
|
1009
1013
|
// unchanged. Single-mode shape: one origin-relative file per deployment.
|
|
1010
1014
|
uiHintsFile?: string,
|
|
1011
|
-
// Optional
|
|
1015
|
+
// Optional geocoding place index, as returned by
|
|
1016
|
+
// `Capability_Geocoding_AwsLocation.make`. When set, the deploy provisions a
|
|
1012
1017
|
// public geocoder Lambda Function URL (Geocoder_AwsLocation) and threads its
|
|
1013
1018
|
// URL into config.json as `geocoderEndpoint`. Unset ⇒ no service, field
|
|
1014
1019
|
// omitted (byte-identical config.json).
|
|
1015
|
-
geocoderPlaceIndex?:
|
|
1016
|
-
//
|
|
1017
|
-
//
|
|
1018
|
-
//
|
|
1019
|
-
// `uploadEndpoint
|
|
1020
|
-
|
|
1021
|
-
//
|
|
1022
|
-
//
|
|
1023
|
-
|
|
1024
|
-
//
|
|
1025
|
-
//
|
|
1026
|
-
//
|
|
1027
|
-
|
|
1020
|
+
geocoderPlaceIndex?: ReventlessInfra.Platform.geocoderIndex,
|
|
1021
|
+
// Optional object store for direct-to-S3 uploads, as returned by
|
|
1022
|
+
// `Capability_ObjectStore_S3.make`. When set, the deploy provisions the
|
|
1023
|
+
// presign service (Upload_Presign_S3) against it, threads that service's URL
|
|
1024
|
+
// into config.json as `uploadEndpoint`, and fronts the store read-only on
|
|
1025
|
+
// the host-shell CloudFront distribution under the presign service's own
|
|
1026
|
+
// prefix (a `{prefix}/*` ordered cache behavior + origin + scoped
|
|
1027
|
+
// BucketPolicy, private, OAC-read).
|
|
1028
|
+
//
|
|
1029
|
+
// One field replaces the former `enableUploads` / `uploadBucketName` /
|
|
1030
|
+
// `servedBuckets` trio, which were three ways of saying one thing and left
|
|
1031
|
+
// the prefix to be restated by hand on the serve side. The framework now
|
|
1032
|
+
// writes the prefix once and consumes it on both sides, so a mismatch is
|
|
1033
|
+
// unrepresentable rather than merely discouraged.
|
|
1034
|
+
uploadBucket?: ReventlessInfra.Platform.objectStore,
|
|
1028
1035
|
}
|
|
1029
1036
|
|
|
1030
1037
|
// Merged-mode outputs of deployPlatform — the merged API(s), plus the
|
|
@@ -1483,10 +1490,28 @@ module MakeWithConfig = (
|
|
|
1483
1490
|
| _ => None
|
|
1484
1491
|
}
|
|
1485
1492
|
|
|
1493
|
+
// A served store is fronted under exactly the prefix the presign service
|
|
1494
|
+
// roots its keys at, read from the service's own constant. This is the
|
|
1495
|
+
// whole reason the store arrives as one value: the app no longer restates
|
|
1496
|
+
// the prefix, so it cannot restate it wrongly.
|
|
1497
|
+
let servedBuckets = switch cfg.uploadBucket {
|
|
1498
|
+
| Some(store) => [
|
|
1499
|
+
{
|
|
1500
|
+
ReventlessInfra.Platform.prefix: Upload_Presign_S3.defaultServedPrefix,
|
|
1501
|
+
bucketId: store.bucketId,
|
|
1502
|
+
bucketArn: store.bucketArn,
|
|
1503
|
+
bucketRegionalDomainName: store.bucketRegionalDomainName,
|
|
1504
|
+
},
|
|
1505
|
+
]
|
|
1506
|
+
| None => []
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1486
1509
|
let {distributionUrl, bucketName} = Plugin_Stack.makeUiBundleDistribution(
|
|
1487
1510
|
~pluginId="host-ui",
|
|
1488
|
-
~bundleVersion=cfg.bundleVersion,
|
|
1489
|
-
~assetsDir=cfg.assetsDir
|
|
1511
|
+
~bundleVersion=cfg.bundleVersion->Option.getOr(version),
|
|
1512
|
+
~assetsDir=cfg.assetsDir->Option.getOr(
|
|
1513
|
+
Util_Bundle.resolvePackageRoot("@reventlessdev/reventless-host-shell") ++ "/dist",
|
|
1514
|
+
),
|
|
1490
1515
|
~spaFallback=true,
|
|
1491
1516
|
~stableName=true,
|
|
1492
1517
|
// The explicit BucketObjects below write the production config.json
|
|
@@ -1500,8 +1525,8 @@ module MakeWithConfig = (
|
|
|
1500
1525
|
// Served buckets front the host-shell distribution with `{prefix}/*`
|
|
1501
1526
|
// read paths (private, OAC-read) — same origin as the SPA, so served
|
|
1502
1527
|
// objects are addressable by same-origin relative URL with no config.json
|
|
1503
|
-
// change.
|
|
1504
|
-
~servedBuckets
|
|
1528
|
+
// change. No upload store ⇒ [] ⇒ distribution byte-identical to today.
|
|
1529
|
+
~servedBuckets,
|
|
1505
1530
|
)
|
|
1506
1531
|
|
|
1507
1532
|
let regionStr =
|
|
@@ -1528,21 +1553,20 @@ module MakeWithConfig = (
|
|
|
1528
1553
|
// index is configured; its resolved URL becomes config.json's
|
|
1529
1554
|
// `geocoderEndpoint`. Unset ⇒ Output<None> ⇒ field omitted below.
|
|
1530
1555
|
let geocoderEndpointOutput: Pulumi.Output.t<option<string>> = switch cfg.geocoderPlaceIndex {
|
|
1531
|
-
| Some(
|
|
1532
|
-
Geocoder_AwsLocation.make(
|
|
1556
|
+
| Some(index) =>
|
|
1557
|
+
Geocoder_AwsLocation.make(
|
|
1558
|
+
~placeIndexName=index.indexName,
|
|
1559
|
+
).url->Pulumi.Output.apply(u => Some(u))
|
|
1533
1560
|
| None => Pulumi.Output.make(None)
|
|
1534
1561
|
}
|
|
1535
1562
|
|
|
1536
|
-
// Optional public upload presign Function URL
|
|
1537
|
-
//
|
|
1538
|
-
// `uploadEndpoint`.
|
|
1539
|
-
let uploadEndpointOutput: Pulumi.Output.t<option<string>> = switch
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
)
|
|
1543
|
-
| (Some(true), Some(bucketName)) =>
|
|
1544
|
-
Upload_Presign_S3.make(~bucketName).url->Pulumi.Output.apply(u => Some(u))
|
|
1545
|
-
| _ => Pulumi.Output.make(None)
|
|
1563
|
+
// Optional public upload presign Function URL, provisioned against the
|
|
1564
|
+
// configured store; its resolved URL becomes config.json's
|
|
1565
|
+
// `uploadEndpoint`. No store ⇒ Output<None> ⇒ field omitted below.
|
|
1566
|
+
let uploadEndpointOutput: Pulumi.Output.t<option<string>> = switch cfg.uploadBucket {
|
|
1567
|
+
| Some(store) =>
|
|
1568
|
+
Upload_Presign_S3.make(~bucketName=store.bucketName).url->Pulumi.Output.apply(u => Some(u))
|
|
1569
|
+
| None => Pulumi.Output.make(None)
|
|
1546
1570
|
}
|
|
1547
1571
|
|
|
1548
1572
|
let configJsonContent =
|
|
@@ -1580,6 +1604,13 @@ module MakeWithConfig = (
|
|
|
1580
1604
|
[
|
|
1581
1605
|
("domainApiEventsEndpoint", JSON.Encode.string(ep)),
|
|
1582
1606
|
("platformApiEventsEndpoint", JSON.Encode.string(ep)),
|
|
1607
|
+
// Capability gate: present iff the API carries the
|
|
1608
|
+
// client-publishable namespace — clients hide publish-dependent
|
|
1609
|
+
// features (presence, transient chat) when absent.
|
|
1610
|
+
(
|
|
1611
|
+
"clientEventsNamespace",
|
|
1612
|
+
JSON.Encode.string(AppSync_EventsApi.clientNamespaceName),
|
|
1613
|
+
),
|
|
1583
1614
|
],
|
|
1584
1615
|
)
|
|
1585
1616
|
| None => fields
|
package/src/Platform.res.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/co
|
|
|
21
21
|
import * as DcbBackend$ReventlessAws from "./adapter/DcbEventLog/DcbBackend.res.mjs";
|
|
22
22
|
import * as ReadModel$ReventlessCore from "@reventlessdev/reventless-core/src/components/ReadModel/ReadModel.res.mjs";
|
|
23
23
|
import * as PluginSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginSpec.res.mjs";
|
|
24
|
+
import * as Util_Bundle$ReventlessAws from "./util/Util_Bundle.res.mjs";
|
|
24
25
|
import * as Plugin_Stack$ReventlessAws from "./plugin/stack/Plugin_Stack.res.mjs";
|
|
25
26
|
import * as UiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments.res.mjs";
|
|
26
27
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
@@ -883,19 +884,25 @@ function MakeWithConfig(Config) {
|
|
|
883
884
|
} else {
|
|
884
885
|
customDomain = undefined;
|
|
885
886
|
}
|
|
886
|
-
let
|
|
887
|
+
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, [
|
|
887
895
|
"config.json",
|
|
888
896
|
"ui-hints.json"
|
|
889
|
-
], customDomain,
|
|
897
|
+
], customDomain, servedBuckets);
|
|
890
898
|
let bucketName = match$3.bucketName;
|
|
891
899
|
let regionStr = Stdlib_Option.getOr(new Pulumi.Config("aws").get("region"), "unknown");
|
|
892
900
|
let cognitoPool = Platform_Stack$ReventlessAws.resolveCognitoUserPool();
|
|
893
901
|
let domainEventsEndpointOutput = domainEventsApiOpt !== undefined ? AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt).apply(ep => ep + "/event") : Pulumi.output(undefined);
|
|
894
|
-
let
|
|
895
|
-
let geocoderEndpointOutput =
|
|
896
|
-
let
|
|
897
|
-
let
|
|
898
|
-
let uploadEndpointOutput = match$4 !== undefined && match$4 && match$5 !== undefined ? Upload_Presign_S3$ReventlessAws.make(match$5, undefined, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
|
|
902
|
+
let index = hostUiBundle.geocoderPlaceIndex;
|
|
903
|
+
let geocoderEndpointOutput = index !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(index.indexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
|
|
904
|
+
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);
|
|
899
906
|
let configJsonContent = Pulumi.all([
|
|
900
907
|
Pulumi.all([
|
|
901
908
|
resolvedDomainApiEndpoint,
|
|
@@ -949,6 +956,10 @@ function MakeWithConfig(Config) {
|
|
|
949
956
|
[
|
|
950
957
|
"platformApiEventsEndpoint",
|
|
951
958
|
eventsEpOpt
|
|
959
|
+
],
|
|
960
|
+
[
|
|
961
|
+
"clientEventsNamespace",
|
|
962
|
+
AppSync_EventsApi$ReventlessAws.clientNamespaceName
|
|
952
963
|
]
|
|
953
964
|
]) : fields;
|
|
954
965
|
let withGeocoder = geocoderEpOpt !== undefined ? withEvents.concat([[
|
|
@@ -1866,19 +1877,25 @@ function Make($star) {
|
|
|
1866
1877
|
} else {
|
|
1867
1878
|
customDomain = undefined;
|
|
1868
1879
|
}
|
|
1869
|
-
let
|
|
1880
|
+
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, [
|
|
1870
1888
|
"config.json",
|
|
1871
1889
|
"ui-hints.json"
|
|
1872
|
-
], customDomain,
|
|
1890
|
+
], customDomain, servedBuckets);
|
|
1873
1891
|
let bucketName = match$3.bucketName;
|
|
1874
1892
|
let regionStr = Stdlib_Option.getOr(new Pulumi.Config("aws").get("region"), "unknown");
|
|
1875
1893
|
let cognitoPool = Platform_Stack$ReventlessAws.resolveCognitoUserPool();
|
|
1876
1894
|
let domainEventsEndpointOutput = domainEventsApiOpt !== undefined ? AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt).apply(ep => ep + "/event") : Pulumi.output(undefined);
|
|
1877
|
-
let
|
|
1878
|
-
let geocoderEndpointOutput =
|
|
1879
|
-
let
|
|
1880
|
-
let
|
|
1881
|
-
let uploadEndpointOutput = match$4 !== undefined && match$4 && match$5 !== undefined ? Upload_Presign_S3$ReventlessAws.make(match$5, undefined, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
|
|
1895
|
+
let index = hostUiBundle.geocoderPlaceIndex;
|
|
1896
|
+
let geocoderEndpointOutput = index !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(index.indexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
|
|
1897
|
+
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);
|
|
1882
1899
|
let configJsonContent = Pulumi.all([
|
|
1883
1900
|
Pulumi.all([
|
|
1884
1901
|
resolvedDomainApiEndpoint,
|
|
@@ -1932,6 +1949,10 @@ function Make($star) {
|
|
|
1932
1949
|
[
|
|
1933
1950
|
"platformApiEventsEndpoint",
|
|
1934
1951
|
eventsEpOpt
|
|
1952
|
+
],
|
|
1953
|
+
[
|
|
1954
|
+
"clientEventsNamespace",
|
|
1955
|
+
AppSync_EventsApi$ReventlessAws.clientNamespaceName
|
|
1935
1956
|
]
|
|
1936
1957
|
]) : fields;
|
|
1937
1958
|
let withGeocoder = geocoderEpOpt !== undefined ? withEvents.concat([[
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
/** AppSync_EventsApi — creates an IAM-authenticated AppSync Events (Pub/Sub) API
|
|
2
|
-
with a `default` channel namespace
|
|
2
|
+
with a `default` channel namespace and a client-publishable `client`
|
|
3
|
+
namespace.
|
|
3
4
|
|
|
4
5
|
This is a companion to the AppSync GraphQL API. Sources A and B push
|
|
5
6
|
real-time events here; clients subscribe to channels such as
|
|
6
7
|
`/default/{topicRoot}/{entityKey}` — see `StateTopic_AppSync.res` for
|
|
7
8
|
channel layout.
|
|
8
9
|
|
|
10
|
+
Namespace auth asymmetry:
|
|
11
|
+
- `default` namespace — publish AWS_IAM only (the SigV4-signed Lambda
|
|
12
|
+
publishers); a browser must never be able to forge a change descriptor.
|
|
13
|
+
- `client` namespace — publish + subscribe Cognito (and IAM): ephemeral
|
|
14
|
+
client→client fan-out (presence, typing, transient chat). Payloads are
|
|
15
|
+
opaque to the platform and client-asserted — consumers treat them as
|
|
16
|
+
untrusted input. See docs/plans/events-client-publish-channels.md.
|
|
17
|
+
|
|
9
18
|
Usage (in platform/plugin builder after the GraphQL API is created):
|
|
10
19
|
let eventsApi = AppSync_EventsApi.make(
|
|
11
20
|
~name="DomainEventsApi",
|
|
@@ -73,8 +82,16 @@ type t = {
|
|
|
73
82
|
api: Api.t,
|
|
74
83
|
/** The `default` channel namespace created on the API. */
|
|
75
84
|
defaultNamespace: option<ChannelNamespace.t>,
|
|
85
|
+
/** The client-publishable `client` namespace. Absent on plugin-stack
|
|
86
|
+
phantoms (namespaces live on the platform stack's API). */
|
|
87
|
+
clientNamespace?: ChannelNamespace.t,
|
|
76
88
|
}
|
|
77
89
|
|
|
90
|
+
/** Namespace name for client-publishable channels (paths under `/client/`).
|
|
91
|
+
Emitted into the host-ui config as `clientEventsNamespace` so clients can
|
|
92
|
+
gate publish-dependent features on its presence. */
|
|
93
|
+
let clientNamespaceName = "client"
|
|
94
|
+
|
|
78
95
|
/** Create an AppSync Events API with dual auth (Cognito + AWS_IAM) and a
|
|
79
96
|
`default` channel namespace.
|
|
80
97
|
|
|
@@ -161,7 +178,31 @@ let make = (
|
|
|
161
178
|
~opts=Some({...opts, parent: api->Pulumi.Resource.makeFromJs}),
|
|
162
179
|
)
|
|
163
180
|
|
|
164
|
-
|
|
181
|
+
// Client-publishable namespace: per-namespace auth modes override the
|
|
182
|
+
// API-level `defaultPublishAuthModes` (IAM-only), so authenticated browsers
|
|
183
|
+
// can publish ephemeral payloads on `/client/**` while `/default/**` change
|
|
184
|
+
// descriptors stay Lambda-only.
|
|
185
|
+
let clientAuthModes: Pulumi.Input.t<
|
|
186
|
+
array<Pulumi.Input.t<ChannelNamespace.authMode>>,
|
|
187
|
+
> =
|
|
188
|
+
[
|
|
189
|
+
({authType: Api.amazonCognitoUserPools->Pulumi.Input.make}: ChannelNamespace.authMode)
|
|
190
|
+
->Pulumi.Input.make,
|
|
191
|
+
({authType: Api.awsIam->Pulumi.Input.make}: ChannelNamespace.authMode)
|
|
192
|
+
->Pulumi.Input.make,
|
|
193
|
+
]->Pulumi.Input.make
|
|
194
|
+
let clientNamespace = ChannelNamespace.make(
|
|
195
|
+
~name=name ++ "ClientNS",
|
|
196
|
+
~args={
|
|
197
|
+
apiId: api.apiId->Pulumi.Output.asInput,
|
|
198
|
+
name: clientNamespaceName->Pulumi.Input.make,
|
|
199
|
+
publishAuthModes: clientAuthModes,
|
|
200
|
+
subscribeAuthModes: clientAuthModes,
|
|
201
|
+
},
|
|
202
|
+
~opts=Some({...opts, parent: api->Pulumi.Resource.makeFromJs}),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
{name, api, defaultNamespace: Some(defaultNamespace), clientNamespace}
|
|
165
206
|
}
|
|
166
207
|
|
|
167
208
|
/** HTTP endpoint URL for use as Lambda `APPSYNC_ENDPOINT` env var.
|
|
@@ -17,6 +17,8 @@ function clearSubscribeAuth() {
|
|
|
17
17
|
subscribeAuthRef.contents = undefined;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
let clientNamespaceName = "client";
|
|
21
|
+
|
|
20
22
|
function make(name, cognitoUserPoolId, awsRegion, opts) {
|
|
21
23
|
let iamMode = {
|
|
22
24
|
authType: AwsNative_AppSync_Api$PulumiAws.awsIam
|
|
@@ -69,10 +71,26 @@ function make(name, cognitoUserPoolId, awsRegion, opts) {
|
|
|
69
71
|
codeHandlers: codeHandlersInput,
|
|
70
72
|
handlerConfigs: handlerConfigsInput
|
|
71
73
|
}, (newrecord.parent = api, newrecord));
|
|
74
|
+
let clientAuthModes = [
|
|
75
|
+
{
|
|
76
|
+
authType: AwsNative_AppSync_Api$PulumiAws.amazonCognitoUserPools
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
authType: AwsNative_AppSync_Api$PulumiAws.awsIam
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
let newrecord$1 = {...opts};
|
|
83
|
+
let clientNamespace = new (AwsNative.appsync.ChannelNamespace)(name + "ClientNS", {
|
|
84
|
+
apiId: api.apiId,
|
|
85
|
+
name: clientNamespaceName,
|
|
86
|
+
publishAuthModes: clientAuthModes,
|
|
87
|
+
subscribeAuthModes: clientAuthModes
|
|
88
|
+
}, (newrecord$1.parent = api, newrecord$1));
|
|
72
89
|
return {
|
|
73
90
|
name: name,
|
|
74
91
|
api: api,
|
|
75
|
-
defaultNamespace: defaultNamespace
|
|
92
|
+
defaultNamespace: defaultNamespace,
|
|
93
|
+
clientNamespace: clientNamespace
|
|
76
94
|
};
|
|
77
95
|
}
|
|
78
96
|
|
|
@@ -90,6 +108,7 @@ export {
|
|
|
90
108
|
subscribeAuthRef,
|
|
91
109
|
registerSubscribeAuth,
|
|
92
110
|
clearSubscribeAuth,
|
|
111
|
+
clientNamespaceName,
|
|
93
112
|
make,
|
|
94
113
|
httpEndpoint,
|
|
95
114
|
}
|
|
@@ -25,13 +25,23 @@ type serviceOutputs = {
|
|
|
25
25
|
resources: array<Pulumi.Output.t<string>>,
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
The prefix presigned object keys are rooted at, and therefore the prefix the
|
|
30
|
+
store must be served under for the returned `/{key}` ref to resolve.
|
|
31
|
+
|
|
32
|
+
Exported so the serve side reads the same constant the mint side writes. The two
|
|
33
|
+
have to agree exactly: a mismatch deploys green and 404s every object, because
|
|
34
|
+
nothing at deploy time compares a minted key against a cache behavior.
|
|
35
|
+
*/
|
|
36
|
+
let defaultServedPrefix = "uploads"
|
|
37
|
+
|
|
28
38
|
let make = (
|
|
29
39
|
~bucketName: Pulumi.Input.t<string>,
|
|
30
40
|
~corsOrigins: array<string>=["*"],
|
|
31
41
|
// Prefix the presigned object keys are rooted at; must match the served
|
|
32
42
|
// bucket's CloudFront `{prefix}/*` behavior so the returned `/{key}` ref
|
|
33
|
-
// resolves.
|
|
34
|
-
~servedPrefix: string=
|
|
43
|
+
// resolves.
|
|
44
|
+
~servedPrefix: string=defaultServedPrefix,
|
|
35
45
|
~opts=?,
|
|
36
46
|
): serviceOutputs => {
|
|
37
47
|
let serviceName = "UploadPresignService"
|
|
@@ -12,9 +12,11 @@ import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/sr
|
|
|
12
12
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
13
13
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
14
14
|
|
|
15
|
+
let defaultServedPrefix = "uploads";
|
|
16
|
+
|
|
15
17
|
function make(bucketName, corsOriginsOpt, servedPrefixOpt, opts) {
|
|
16
18
|
let corsOrigins = corsOriginsOpt !== undefined ? corsOriginsOpt : ["*"];
|
|
17
|
-
let servedPrefix = servedPrefixOpt !== undefined ? servedPrefixOpt :
|
|
19
|
+
let servedPrefix = servedPrefixOpt !== undefined ? servedPrefixOpt : defaultServedPrefix;
|
|
18
20
|
let serviceName = "UploadPresignService";
|
|
19
21
|
let opts$1 = Stdlib_Option.map(opts, Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions);
|
|
20
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);
|
|
@@ -102,6 +104,7 @@ function make(bucketName, corsOriginsOpt, servedPrefixOpt, opts) {
|
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
export {
|
|
107
|
+
defaultServedPrefix,
|
|
105
108
|
make,
|
|
106
109
|
}
|
|
107
110
|
/* @pulumi/aws Not a pure module */
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// A geocoding place index, provisioned with the framework's house conventions
|
|
2
|
+
// applied — the address-search backing for geo-point command inputs.
|
|
3
|
+
//
|
|
4
|
+
// Beyond the attribution tags every framework resource carries, this helper
|
|
5
|
+
// exists to move two settings out of application code. `dataSource` picks the
|
|
6
|
+
// geospatial data provider and `intendedUse` decides whether results may be
|
|
7
|
+
// stored; between them they determine the licensing terms, the per-request
|
|
8
|
+
// price, and how long returned data may be retained. Those are deployment
|
|
9
|
+
// decisions — a change of provider can be a contractual matter — so they read
|
|
10
|
+
// from deploy-time config the way the host-shell's custom domain does, rather
|
|
11
|
+
// than being literals in an app's `Main.res`.
|
|
12
|
+
//
|
|
13
|
+
// Config keys (env var `REVENTLESS_<SCREAMING_SNAKE>` → `Pulumi.local.yaml`,
|
|
14
|
+
// see `Util_LocalConfig`):
|
|
15
|
+
// - `geocoderDataSource` — "Esri" | "Grab" | "Here". Default "Esri".
|
|
16
|
+
// - `geocoderIntendedUse` — "SingleUse" | "Storage". Default "SingleUse".
|
|
17
|
+
//
|
|
18
|
+
// The defaults reproduce what deployments wrote by hand, so adopting this
|
|
19
|
+
// helper is not a licensing-tier change.
|
|
20
|
+
|
|
21
|
+
open PulumiAws
|
|
22
|
+
|
|
23
|
+
let defaultDataSource = "Esri"
|
|
24
|
+
let defaultIntendedUse = "SingleUse"
|
|
25
|
+
|
|
26
|
+
/** Create a place index with framework attribution tags and config-driven
|
|
27
|
+
provider/retention settings. */
|
|
28
|
+
let make = (~name: string, ~opts: option<Pulumi.CustomResourceOptions.t>=?): ReventlessInfra.Platform.geocoderIndex => {
|
|
29
|
+
let dataSource = Util_LocalConfig.get("geocoderDataSource")->Option.getOr(defaultDataSource)
|
|
30
|
+
let intendedUse = Util_LocalConfig.get("geocoderIntendedUse")->Option.getOr(defaultIntendedUse)
|
|
31
|
+
|
|
32
|
+
let index = Location.PlaceIndex.make(
|
|
33
|
+
~name,
|
|
34
|
+
~args={
|
|
35
|
+
indexName: Pulumi.Input.make(name),
|
|
36
|
+
dataSource: Pulumi.Input.make(dataSource),
|
|
37
|
+
dataSourceConfiguration: Pulumi.Input.make({
|
|
38
|
+
Location.PlaceIndex.intendedUse: Pulumi.Input.make(intendedUse),
|
|
39
|
+
}),
|
|
40
|
+
tags: AWS.Tags.make(
|
|
41
|
+
~name,
|
|
42
|
+
~kind=ReventlessCore.ComponentType.Platform,
|
|
43
|
+
~role=Other("Geocoder"),
|
|
44
|
+
~scope=Platform,
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
~opts?,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
{indexName: index.indexName->Pulumi.Output.asInput}
|
|
51
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Aws from "@pulumi/aws";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
6
|
+
import * as AWS_Tags$ReventlessAws from "../adapter/AWS_Tags.res.mjs";
|
|
7
|
+
import * as Util_LocalConfig$ReventlessAws from "../util/Util_LocalConfig.res.mjs";
|
|
8
|
+
|
|
9
|
+
let defaultDataSource = "Esri";
|
|
10
|
+
|
|
11
|
+
let defaultIntendedUse = "SingleUse";
|
|
12
|
+
|
|
13
|
+
function make(name, opts) {
|
|
14
|
+
let dataSource = Stdlib_Option.getOr(Util_LocalConfig$ReventlessAws.get("geocoderDataSource"), defaultDataSource);
|
|
15
|
+
let intendedUse = Stdlib_Option.getOr(Util_LocalConfig$ReventlessAws.get("geocoderIntendedUse"), defaultIntendedUse);
|
|
16
|
+
let index = new (Aws.location.PlaceIndex)(name, {
|
|
17
|
+
indexName: name,
|
|
18
|
+
dataSource: dataSource,
|
|
19
|
+
dataSourceConfiguration: {
|
|
20
|
+
intendedUse: intendedUse
|
|
21
|
+
},
|
|
22
|
+
tags: AWS_Tags$ReventlessAws.make(name, "Platform", {
|
|
23
|
+
TAG: "Other",
|
|
24
|
+
_0: "Geocoder"
|
|
25
|
+
}, "Platform", undefined, undefined, undefined, undefined)
|
|
26
|
+
}, opts !== undefined ? Primitive_option.valFromOption(opts) : undefined);
|
|
27
|
+
return {
|
|
28
|
+
indexName: index.indexName
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
defaultDataSource,
|
|
34
|
+
defaultIntendedUse,
|
|
35
|
+
make,
|
|
36
|
+
}
|
|
37
|
+
/* @pulumi/aws Not a pure module */
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// An object store, provisioned with the framework's house conventions applied.
|
|
2
|
+
//
|
|
3
|
+
// Deployments used to hand-write this bucket with raw Pulumi, which meant every
|
|
4
|
+
// convention the framework applies to its own buckets had to be remembered by
|
|
5
|
+
// hand — and the attribution tags in particular were routinely forgotten. Tags
|
|
6
|
+
// are not cosmetic here: operational tooling discovers a platform's resources
|
|
7
|
+
// *only* through `reventless:platform` + `reventless:environment` tag filters
|
|
8
|
+
// against the Resource Groups Tagging API, so an untagged bucket is invisible
|
|
9
|
+
// to it. A store wipe would then leave every uploaded object behind while the
|
|
10
|
+
// events referencing them are gone.
|
|
11
|
+
//
|
|
12
|
+
// The returned record is what `deployPlatform`'s `~hostUiBundle` takes, so the
|
|
13
|
+
// bucket is described once and both the write path (presigned PUTs) and the
|
|
14
|
+
// read path (served read-only from the UI origin) are derived from it.
|
|
15
|
+
//
|
|
16
|
+
// Encryption and versioning are deliberately left at the account/AWS defaults,
|
|
17
|
+
// matching the framework's own buckets (`TaskBucket_S3`, the plugin bundle
|
|
18
|
+
// bucket): S3 applies AES256 to new buckets by default, and versioning is
|
|
19
|
+
// one-way — once enabled it can be suspended but never removed — so it changes
|
|
20
|
+
// both cost and delete semantics and belongs to a deployment's judgement rather
|
|
21
|
+
// than to a framework default.
|
|
22
|
+
//
|
|
23
|
+
// Follow-up, once a store's lifecycle follows a declaration rather than an
|
|
24
|
+
// explicit line of app code: `protect` / `retainOnDelete`, so an accidental
|
|
25
|
+
// removal of that declaration cannot destroy a bucket holding live objects.
|
|
26
|
+
|
|
27
|
+
open PulumiAws
|
|
28
|
+
|
|
29
|
+
// Browser direct-PUT needs CORS. These defaults match what deployments wrote by
|
|
30
|
+
// hand; `~corsRules` overrides them for a store the browser never touches.
|
|
31
|
+
let defaultCorsRules: S3.Bucket.corsRules = [
|
|
32
|
+
{
|
|
33
|
+
S3.Bucket.allowedHeaders: ["*"],
|
|
34
|
+
allowedMethods: ["HEAD", "GET", "PUT", "POST"],
|
|
35
|
+
allowedOrigins: ["*"],
|
|
36
|
+
exposeHeaders: ["ETag"],
|
|
37
|
+
maxAgeSeconds: 3000,
|
|
38
|
+
},
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
/** Create an object store bucket with framework attribution tags and an
|
|
42
|
+
all-true public-access block. */
|
|
43
|
+
let make = (
|
|
44
|
+
~name: string,
|
|
45
|
+
~corsRules: S3.Bucket.corsRules=defaultCorsRules,
|
|
46
|
+
~opts: option<Pulumi.CustomResourceOptions.t>=?,
|
|
47
|
+
): ReventlessInfra.Platform.objectStore => {
|
|
48
|
+
let bucket = S3.Bucket.make(
|
|
49
|
+
~name,
|
|
50
|
+
~args={
|
|
51
|
+
corsRules: corsRules->Pulumi.Input.make,
|
|
52
|
+
tags: AWS.Tags.make(
|
|
53
|
+
~name,
|
|
54
|
+
~kind=ReventlessCore.ComponentType.Platform,
|
|
55
|
+
~role=Other("ObjectStore"),
|
|
56
|
+
~scope=Platform,
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
~opts?,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
// A served store is read through the UI's CDN via an origin access control,
|
|
63
|
+
// never directly, so it keeps its own all-true public-access block. Account
|
|
64
|
+
// level S3 defaults happen to cover this today, which makes its absence a
|
|
65
|
+
// latent gap rather than a live exposure — but one that turns live the moment
|
|
66
|
+
// an account default changes.
|
|
67
|
+
let _pab = S3.BucketPublicAccessBlock.make(
|
|
68
|
+
~name=name ++ "-pab",
|
|
69
|
+
~args={
|
|
70
|
+
bucket: bucket.id->Pulumi.Output.asInput,
|
|
71
|
+
blockPublicAcls: Pulumi.Input.make(true),
|
|
72
|
+
blockPublicPolicy: Pulumi.Input.make(true),
|
|
73
|
+
ignorePublicAcls: Pulumi.Input.make(true),
|
|
74
|
+
restrictPublicBuckets: Pulumi.Input.make(true),
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
{
|
|
79
|
+
bucketName: bucket.bucket->Pulumi.Output.asInput,
|
|
80
|
+
bucketId: bucket.id->Pulumi.Output.asInput,
|
|
81
|
+
bucketArn: bucket.arn->Pulumi.Output.asInput,
|
|
82
|
+
bucketRegionalDomainName: bucket.bucketRegionalDomainName->Pulumi.Output.asInput,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Aws from "@pulumi/aws";
|
|
4
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
5
|
+
import * as AWS_Tags$ReventlessAws from "../adapter/AWS_Tags.res.mjs";
|
|
6
|
+
|
|
7
|
+
let defaultCorsRules = [{
|
|
8
|
+
allowedHeaders: ["*"],
|
|
9
|
+
allowedMethods: [
|
|
10
|
+
"HEAD",
|
|
11
|
+
"GET",
|
|
12
|
+
"PUT",
|
|
13
|
+
"POST"
|
|
14
|
+
],
|
|
15
|
+
allowedOrigins: ["*"],
|
|
16
|
+
exposeHeaders: ["ETag"],
|
|
17
|
+
maxAgeSeconds: 3000
|
|
18
|
+
}];
|
|
19
|
+
|
|
20
|
+
function make(name, corsRulesOpt, opts) {
|
|
21
|
+
let corsRules = corsRulesOpt !== undefined ? corsRulesOpt : defaultCorsRules;
|
|
22
|
+
let bucket = new (Aws.s3.Bucket)(name, {
|
|
23
|
+
corsRules: corsRules,
|
|
24
|
+
tags: AWS_Tags$ReventlessAws.make(name, "Platform", {
|
|
25
|
+
TAG: "Other",
|
|
26
|
+
_0: "ObjectStore"
|
|
27
|
+
}, "Platform", undefined, undefined, undefined, undefined)
|
|
28
|
+
}, opts !== undefined ? Primitive_option.valFromOption(opts) : undefined);
|
|
29
|
+
new (Aws.s3.BucketPublicAccessBlock)(name + "-pab", {
|
|
30
|
+
bucket: bucket.id,
|
|
31
|
+
blockPublicAcls: true,
|
|
32
|
+
blockPublicPolicy: true,
|
|
33
|
+
ignorePublicAcls: true,
|
|
34
|
+
restrictPublicBuckets: true
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
bucketName: bucket.bucket,
|
|
38
|
+
bucketId: bucket.id,
|
|
39
|
+
bucketArn: bucket.arn,
|
|
40
|
+
bucketRegionalDomainName: bucket.bucketRegionalDomainName
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
defaultCorsRules,
|
|
46
|
+
make,
|
|
47
|
+
}
|
|
48
|
+
/* @pulumi/aws Not a pure module */
|