@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 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.236",
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.106",
21
- "@reventlessdev/reventless-core": "3.0.0-alpha.186",
22
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.50",
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
- assetsDir: string,
1003
- bundleVersion: string,
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 AWS Location place-index name. When set, the deploy provisions a
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?: Pulumi.Input.t<string>,
1016
- // Opt into the direct-to-S3 upload presign service (Upload_Presign_S3). Both
1017
- // this flag and `uploadBucketName` must be set; the deploy then provisions a
1018
- // public presign Lambda Function URL and threads its URL into config.json as
1019
- // `uploadEndpoint`. Unset no service, field omitted.
1020
- enableUploads?: bool,
1021
- // S3 bucket the upload presign service issues PUT URLs against. Required when
1022
- // `enableUploads` is true.
1023
- uploadBucketName?: Pulumi.Input.t<string>,
1024
- // Buckets served read-only to the UI under a path prefix on the host-shell
1025
- // CloudFront distribution (same-origin, private, OAC-read). Each entry adds a
1026
- // `{prefix}/*` ordered cache behavior + origin + scoped BucketPolicy.
1027
- servedBuckets?: array<ReventlessInfra.Platform.servedBucket>,
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. Unset ⇒ [] ⇒ distribution byte-identical to today.
1504
- ~servedBuckets=cfg.servedBuckets->Option.getOr([]),
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(placeIndexName) =>
1532
- Geocoder_AwsLocation.make(~placeIndexName).url->Pulumi.Output.apply(u => Some(u))
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. Needs both the opt-in flag
1537
- // and a target bucket; its resolved URL becomes config.json's
1538
- // `uploadEndpoint`. Otherwise Output<None> ⇒ field omitted below.
1539
- let uploadEndpointOutput: Pulumi.Output.t<option<string>> = switch (
1540
- cfg.enableUploads,
1541
- cfg.uploadBucketName,
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
@@ -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 match$3 = Plugin_Stack$ReventlessAws.makeUiBundleDistribution("host-ui", hostUiBundle.bundleVersion, hostUiBundle.assetsDir, true, undefined, true, [
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, Stdlib_Option.getOr(hostUiBundle.servedBuckets, []));
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 placeIndexName = hostUiBundle.geocoderPlaceIndex;
895
- let geocoderEndpointOutput = placeIndexName !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(placeIndexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
896
- let match$4 = hostUiBundle.enableUploads;
897
- let match$5 = hostUiBundle.uploadBucketName;
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 match$3 = Plugin_Stack$ReventlessAws.makeUiBundleDistribution("host-ui", hostUiBundle.bundleVersion, hostUiBundle.assetsDir, true, undefined, true, [
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, Stdlib_Option.getOr(hostUiBundle.servedBuckets, []));
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 placeIndexName = hostUiBundle.geocoderPlaceIndex;
1878
- let geocoderEndpointOutput = placeIndexName !== undefined ? Geocoder_AwsLocation$ReventlessAws.make(placeIndexName, undefined, undefined).url.apply(u => u) : Pulumi.output(undefined);
1879
- let match$4 = hostUiBundle.enableUploads;
1880
- let match$5 = hostUiBundle.uploadBucketName;
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
- {name, api, defaultNamespace: Some(defaultNamespace)}
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. Defaults to `uploads`.
34
- ~servedPrefix: string="uploads",
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 : "uploads";
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 */