@reventlessdev/reventless-aws 3.0.0-alpha.172 → 3.0.0-alpha.174

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +6 -6
  3. package/src/Platform.res +77 -7
  4. package/src/Platform.res.mjs +82 -8
  5. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +1 -1
  6. package/src/adapter/DcbEventLog/DcbBackend.res +78 -0
  7. package/src/adapter/DcbEventLog/DcbBackend.res.mjs +53 -0
  8. package/src/adapter/DcbEventLog/DcbEventLogStorage.res +20 -0
  9. package/src/adapter/DcbEventLog/DcbEventLogStorage.res.mjs +21 -1
  10. package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res +43 -0
  11. package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res.mjs +21 -0
  12. package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres_Runtime.res +26 -0
  13. package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres_Runtime.res.mjs +15 -0
  14. package/src/adapter/EventLog/EventLogStorage_Postgres_Runtime.res +23 -0
  15. package/src/adapter/EventLog/EventLogStorage_Postgres_Runtime.res.mjs +14 -0
  16. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +188 -0
  17. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +141 -0
  18. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +72 -0
  19. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +40 -0
  20. package/src/adapter/Runtime/AggregateEntryPoint.mjs +27 -9
  21. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res.mjs +3 -3
  22. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res.mjs +3 -3
  23. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res.mjs +1 -1
  24. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res.mjs +1 -1
  25. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +1 -1
  26. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +1 -1
  27. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +1 -1
  28. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +20 -6
  29. package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res.mjs +1 -1
  30. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +1 -1
  31. package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res.mjs +1 -1
  32. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +52 -0
  33. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +38 -0
  34. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +20 -2
  35. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +1 -1
  36. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +84 -6
  37. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +49 -4
  38. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +1 -1
  39. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +1 -1
  40. package/src/components/Api/AppSync_Adapter.res +72 -8
  41. package/src/components/Api/AppSync_Adapter.res.mjs +45 -6
  42. package/src/components/Plugin.res +1 -1
  43. package/src/components/Plugin.res.mjs +2 -2
  44. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +1 -1
  45. package/src/plugin/runtime/PluginRuntime_Builder.res +20 -0
  46. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +10 -2
  47. package/tests/AppSync_AdapterTest.res +140 -0
  48. package/tests/AppSync_AdapterTest.res.mjs +182 -6
  49. package/tests/PgChangeFeedRelay_RuntimeTest.res +75 -0
  50. package/tests/PgChangeFeedRelay_RuntimeTest.res.mjs +135 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
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.174 (2026-07-05)
7
+
8
+ ### Features
9
+
10
+ * @[@reventless](https://github.com/reventless).systemCallable slice opt-in for deploy-time system callers ([c5ed537](https://github.com/ReventlessDev/reventless-core/commit/c5ed537309f8e4b7d4a4d4af1ed1ec83d060aea3))
11
+ * **reventless-aws:** deploy-time Postgres selection for DCB EventLog (B2.3c) ([c9ac79e](https://github.com/ReventlessDev/reventless-core/commit/c9ac79e890e183874e81ee562ed0938d1efb02df))
12
+ * **reventless-aws:** Postgres change-feed relay auto-wiring (B2.3d) ([62b430f](https://github.com/ReventlessDev/reventless-core/commit/62b430f1e6d8ec1f172de0cca324f7f26aaf5fcb))
13
+
14
+
15
+ # 3.0.0-alpha.173 (2026-07-05)
16
+
17
+ ### Features
18
+
19
+ * **reventless-aws:** aggregate EventLog Postgres runtime + entry-point branch ([fd81ece](https://github.com/ReventlessDev/reventless-core/commit/fd81ece65c2bf6aebf227d74ec2d0f002833d351))
20
+ * **reventless-aws:** DCB EventLog Postgres runtime + entry-point branch (B2.1) ([a6b4f8d](https://github.com/ReventlessDev/reventless-core/commit/a6b4f8d34509ff7213273a1f71f31705b625ff40)), closes [#4](https://github.com/ReventlessDev/reventless-core/issues/4)
21
+ * **reventless-aws:** opt-in [@aws](https://github.com/aws)_iam dual-auth for deploy-time AppSync callers ([94037f1](https://github.com/ReventlessDev/reventless-core/commit/94037f17ae48a33415b02e8e7b178906be8b59a4))
22
+ * **reventless-aws:** Postgres change-feed relay deploy builder + Lambda VPC (B2.3a/b, C1) ([64d67f3](https://github.com/ReventlessDev/reventless-core/commit/64d67f3ecd2c42d59ba814d6d821683ae8c81612))
23
+ * **reventless-aws:** Postgres change-feed relay runtime (B2.2) ([9ccb601](https://github.com/ReventlessDev/reventless-core/commit/9ccb60192457e9da21869e0d1c98fe8dbfb42178))
24
+
25
+
6
26
  # 3.0.0-alpha.172 (2026-07-05)
7
27
 
8
28
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.172",
3
+ "version": "3.0.0-alpha.174",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -11,14 +11,14 @@
11
11
  "@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.20",
12
12
  "@reventlessdev/rescript-effect": "0.1.0-alpha.24",
13
13
  "@reventlessdev/rescript-jest": "1.0.0-alpha.5",
14
+ "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.45",
14
15
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
15
16
  "@reventlessdev/rescript-uuid": "1.1.0-alpha.13",
16
- "@reventlessdev/reventless-core": "3.0.0-alpha.139",
17
- "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.45",
18
- "@reventlessdev/reventless-infra": "3.0.0-alpha.85",
19
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.1",
17
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.87",
18
+ "@reventlessdev/reventless-core": "3.0.0-alpha.141",
20
19
  "@reventlessdev/reventless-interop": "3.0.0-alpha.23",
21
- "@reventlessdev/reventless-spec": "3.0.0-alpha.64"
20
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.3",
21
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.65"
22
22
  },
23
23
  "devDependencies": {
24
24
  "rescript": "^12.3.0",
package/src/Platform.res CHANGED
@@ -61,6 +61,11 @@ module MakeWithConfig = (
61
61
  let splitApi: bool
62
62
  let cloner: bool
63
63
  let commandHandlerConfig: ReventlessCore.Runtime.commandHandlerConfigs
64
+ /** B2.3c platform toggle. When `Some`, every DCB EventLog is Postgres-backed
65
+ (no DynamoDB table/stream; a scheduled `PgChangeFeedRelay` drives propagation),
66
+ while aggregate EventLogs stay on DynamoDB. Provisioned by the app via
67
+ `PgConnection.make`. `None` = all-DynamoDB (unchanged). */
68
+ let pgConnection: option<PgConnection.t>
64
69
  },
65
70
  ): (
66
71
  ReventlessInfra.Platform.T with type api = Types.AppSync.api and type role = Types.AppSync.role
@@ -76,6 +81,17 @@ module MakeWithConfig = (
76
81
  Config.commandHandlerConfig.stateChanges->Option.forEach(({?sync, ?async}) =>
77
82
  PluginRuntime_Builder.setStateChangesConfig(~sync?, ~async?, ())
78
83
  )
84
+ // B2.3c: when a PgConnection is supplied, route ALL DCB EventLogs to Postgres
85
+ // (aggregate EventLogs stay on DynamoDB until the classic feed lands — B2.5) and
86
+ // record the selection for the change-feed relay wiring in makePlatform below.
87
+ // Set BEFORE the Admin/plugin functors run so DcbEventLogStorage.Selectable sees it.
88
+ Config.pgConnection->Option.forEach((pg: PgConnection.t) =>
89
+ DcbBackend.set({
90
+ connectionConfig: pg.connectionConfig,
91
+ securityGroupId: pg.securityGroupId,
92
+ subnetIds: pg.subnetIds,
93
+ })
94
+ )
79
95
  type api = Types.AppSync.api
80
96
  type role = Types.AppSync.role
81
97
  type apiTarget = Domain | Platform
@@ -999,12 +1015,16 @@ module MakeWithConfig = (
999
1015
  })
1000
1016
  },
1001
1017
  // DCB EventLog created hook — extracts DynamoDB table name for DCB CommandTopic Lambda handler.
1002
- onDcbEventLogCreated: dcbEventLogUnknown => {
1003
- let dcbEventLog = dcbEventLogUnknown->asDcbEventLogComponent
1004
- let outputs = dcbEventLog->ReventlessCore.Component.outputs
1005
- let tableResource = outputs.resources->Array.getUnsafe(0)
1006
- PluginRuntime_Builder.registerDcbTableName(tableResource.name)
1007
- },
1018
+ // Postgres-backed DCB logs (B2.3c) create no table, so there is no resource to read:
1019
+ // the DCB command Lambda derives its `dcb_event.log_name` from the plugin name and
1020
+ // reads the PG connection from DcbBackend, so no table-name registration is needed.
1021
+ onDcbEventLogCreated: dcbEventLogUnknown =>
1022
+ if !DcbBackend.isPostgres() {
1023
+ let dcbEventLog = dcbEventLogUnknown->asDcbEventLogComponent
1024
+ let outputs = dcbEventLog->ReventlessCore.Component.outputs
1025
+ let tableResource = outputs.resources->Array.getUnsafe(0)
1026
+ PluginRuntime_Builder.registerDcbTableName(tableResource.name)
1027
+ },
1008
1028
  // DCB CommandTopic created hook — extracts SQS queue URL for slice builders.
1009
1029
  onDcbCommandTopicCreated: dcbCommandTopicUnknown => {
1010
1030
  let commandTopic = dcbCommandTopicUnknown->asDcbCommandTopicComponent
@@ -1124,7 +1144,7 @@ module MakeWithConfig = (
1124
1144
  QueryEngine.DynamoDb,
1125
1145
  ClonerRunner.Fargate,
1126
1146
  PluginRuntime_Builder.Make(EventCollectorChannel),
1127
- DcbEventLogStorage.DynamoDb,
1147
+ DcbEventLogStorage.Selectable,
1128
1148
  EventTopicPublisher.DynamoDbStream,
1129
1149
  CommandTopicChannel.SQS_Sync,
1130
1150
  CommandTopicChannel.SQS_Async,
@@ -1210,6 +1230,47 @@ module MakeWithConfig = (
1210
1230
  type mcpSupported = | @as(true) McpSupported | @as(false) McpNotSupported
1211
1231
  let mcpSupported = McpNotSupported
1212
1232
 
1233
+ // B2.3d: provision the Postgres change-feed relay when the DCB backend is Postgres.
1234
+ // Called after plugins are built (makePlatform monolithic, or deployPlugin per
1235
+ // stack). Each Postgres DCB log registered {logName, partitionTag} during its
1236
+ // plugin build (DcbEventLogStorage_Postgres.make), and its plugin's EventCollector
1237
+ // queue was attached in forPluginEventCollector — so the registry is complete here.
1238
+ // One shared relay Lambda drains all logs from their per-log dcb_subscription
1239
+ // checkpoints and feeds each plugin's collector queue.
1240
+ let provisionPgChangeFeedRelay = () =>
1241
+ switch DcbBackend.get() {
1242
+ | Some({connectionConfig, securityGroupId, subnetIds}) =>
1243
+ let relayLogs =
1244
+ DcbBackend.getRelayLogs()->Array.filterMap(entry =>
1245
+ switch (entry.collectorQueueUrl, entry.collectorQueueArn) {
1246
+ | (Some(targetQueueUrl), Some(targetQueueArn)) =>
1247
+ Some({
1248
+ PgChangeFeedRelay_Builder.connectionConfig,
1249
+ logName: entry.logName,
1250
+ subscriber: "aws-eventcollector-relay",
1251
+ partitionTag: entry.partitionTag,
1252
+ targetQueueUrl,
1253
+ targetQueueArn,
1254
+ })
1255
+ | _ =>
1256
+ log.warn(
1257
+ ~comp="Platform",
1258
+ `PgChangeFeedRelay: DCB log ${entry.logName} has no EventCollector queue — skipping (its events will not propagate)`,
1259
+ )
1260
+ None
1261
+ }
1262
+ )
1263
+ if relayLogs->Array.length > 0 {
1264
+ let _ = PgChangeFeedRelay_Builder.make(
1265
+ ~name="PgChangeFeedRelay",
1266
+ ~logs=relayLogs,
1267
+ ~securityGroupId,
1268
+ ~subnetIds,
1269
+ )
1270
+ }
1271
+ | None => ()
1272
+ }
1273
+
1213
1274
  // In split mode, create a dedicated core AppSync API and push the core schema.
1214
1275
  // In unified mode, makePlatform is a no-op (schema stitching handled by events).
1215
1276
  let makePlatform = (~version, ~plugins: array<module(PluginMaker)>) => {
@@ -1321,6 +1382,10 @@ module MakeWithConfig = (
1321
1382
  | None => ()
1322
1383
  }
1323
1384
 
1385
+ // B2.3d: provision the Postgres change-feed relay (monolithic mode — all plugins
1386
+ // built above, so the DcbBackend relay registry is complete).
1387
+ provisionPgChangeFeedRelay()
1388
+
1324
1389
  // Admin schema push is fired by preAdminResolversSchemaHook from inside
1325
1390
  // Admin.construct (with createResolvers gated on it). Only exports below.
1326
1391
  if Config.splitApi {
@@ -1881,6 +1946,10 @@ module MakeWithConfig = (
1881
1946
  let pluginOutputs = pluginComponent->ReventlessCore.Component.outputs
1882
1947
  ReventlessCore.Plugin_Helpers.exportPluginOutputs(pluginOutputs)
1883
1948
 
1949
+ // B2.3d: provision the Postgres change-feed relay (plugin-stack mode — this
1950
+ // plugin's Postgres DCB log(s) + collector queue were registered during P.make()).
1951
+ provisionPgChangeFeedRelay()
1952
+
1884
1953
  // Zero-downtime handover (Part 3.1): fire ONE synthetic heartbeat for the
1885
1954
  // just-deployed plugin so its new version runs the connect handshake within
1886
1955
  // seconds instead of waiting for the CloudWatch heartbeat rule's first
@@ -1936,5 +2005,6 @@ module Make = (): (
1936
2005
  let splitApi = true
1937
2006
  let cloner = false
1938
2007
  let commandHandlerConfig: ReventlessCore.Runtime.commandHandlerConfigs = {}
2008
+ let pgConnection = None
1939
2009
  })
1940
2010
  }
@@ -22,6 +22,7 @@ import * as AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/adm
22
22
  import * as Scheduler$ReventlessAws from "./components/Scheduler.res.mjs";
23
23
  import * as Aggregate$ReventlessCore from "@reventlessdev/reventless-core/src/components/Aggregate/Aggregate.res.mjs";
24
24
  import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
25
+ import * as DcbBackend$ReventlessAws from "./adapter/DcbEventLog/DcbBackend.res.mjs";
25
26
  import * as ReadModel$ReventlessCore from "@reventlessdev/reventless-core/src/components/ReadModel/ReadModel.res.mjs";
26
27
  import * as PluginSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginSpec.res.mjs";
27
28
  import * as Api_Builder$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/Api_Builder.res.mjs";
@@ -42,6 +43,7 @@ import * as AppSync_EventsApi$ReventlessAws from "./adapter/Api/AppSync_EventsAp
42
43
  import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
43
44
  import * as NoEventMappings$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/NoEventMappings.res.mjs";
44
45
  import * as Util_HostUiDomain$ReventlessAws from "./util/Util_HostUiDomain.res.mjs";
46
+ import * as DcbEventLogStorage$ReventlessAws from "./adapter/DcbEventLog/DcbEventLogStorage.res.mjs";
45
47
  import * as ExtensionMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionMapping.res.mjs";
46
48
  import * as PluginsProjection$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginsProjection.res.mjs";
47
49
  import * as StateTopic_AppSync$ReventlessAws from "./adapter/StateTopic/StateTopic_AppSync.res.mjs";
@@ -60,9 +62,9 @@ import * as Aggregate_Builder_Single$ReventlessAws from "./components/Aggregate_
60
62
  import * as ReadModel_Builder_Single$ReventlessAws from "./components/ReadModel_Builder_Single.res.mjs";
61
63
  import * as StateChangeSlice_Builder$ReventlessAws from "./components/StateChangeSlice_Builder.res.mjs";
62
64
  import * as EventCollectorChannel_SQS$ReventlessAws from "./adapter/EventCollector/EventCollectorChannel_SQS.res.mjs";
65
+ import * as PgChangeFeedRelay_Builder$ReventlessAws from "./adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs";
63
66
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "./adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs";
64
67
  import * as PluginExtensionPointSpec$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/PluginExtensionPointSpec.res.mjs";
65
- import * as DcbEventLogStorage_DynamoDb$ReventlessAws from "./adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res.mjs";
66
68
  import * as Platform_UIFragments_Lambda$ReventlessAws from "./adapter/Api/Platform_UIFragments_Lambda.res.mjs";
67
69
  import * as CommandTopicChannel_SQS_Sync$ReventlessAws from "./adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res.mjs";
68
70
  import * as EventLogSubscription_AppSync$ReventlessAws from "./adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs";
@@ -118,6 +120,11 @@ function MakeWithConfig(Config) {
118
120
  Stdlib_Option.forEach(param.async, AggregateRuntime_Builder_Single_Async$ReventlessAws.setConfig);
119
121
  });
120
122
  Stdlib_Option.forEach(Config.commandHandlerConfig.stateChanges, param => PluginRuntime_Builder$ReventlessAws.setStateChangesConfig(param.sync, param.async, undefined));
123
+ Stdlib_Option.forEach(Config.pgConnection, pg => DcbBackend$ReventlessAws.set({
124
+ connectionConfig: pg.connectionConfig,
125
+ securityGroupId: pg.securityGroupId,
126
+ subnetIds: pg.subnetIds
127
+ }));
121
128
  let currentDeployTarget = {
122
129
  contents: "Domain"
123
130
  };
@@ -598,7 +605,7 @@ function MakeWithConfig(Config) {
598
605
  return Output$Pulumi.flatMap(targetApi, api => Output$Pulumi.flatMap(api.id, apiId => {
599
606
  let runSchemaPush = () => writeAndScanFragments().then(async allPluginFragments => {
600
607
  let baseFragment;
601
- baseFragment = capturedDeployTarget === "Domain" && Config.splitApi ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin");
608
+ baseFragment = capturedDeployTarget === "Domain" && Config.splitApi ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin", undefined);
602
609
  let sdl = GraphQL_Stitcher$ReventlessCore.stitch(baseFragment, allPluginFragments);
603
610
  let currentHash = AppSync_Adapter$ReventlessAws.sha256Hex(sdl);
604
611
  let storedHash = tableNameOpt !== undefined ? await readSchemaHash(tableNameOpt, apiId) : undefined;
@@ -653,7 +660,7 @@ function MakeWithConfig(Config) {
653
660
  let hooks_preAdminResolversSchemaHook = adminBarrier => {
654
661
  let match = splitApiOutputsRef.contents;
655
662
  let targetApi = match !== undefined ? match.platformApi : domainApi;
656
- let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin");
663
+ let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin", undefined);
657
664
  let sdl = GraphQL_Stitcher$ReventlessCore.stitch(adminBaseFragment, []);
658
665
  return Output$Pulumi.flatMap(Pulumi.all([
659
666
  targetApi,
@@ -674,6 +681,9 @@ function MakeWithConfig(Config) {
674
681
  let hooks_inboundAppSyncResolverHook = param => InboundTranslationResolvers_AppSync$ReventlessAws.make(resolveHookedApi(), param.runtime, param.fieldNames, param.opts);
675
682
  let hooks_dcbAppSyncResolverHook = param => CommandGeneratorResolvers_AppSync$ReventlessAws.makeDcb(resolveHookedApi(), param.runtime, param.fieldNames, param.tags, param.opts);
676
683
  let hooks_onDcbEventLogCreated = dcbEventLogUnknown => {
684
+ if (DcbBackend$ReventlessAws.isPostgres()) {
685
+ return;
686
+ }
677
687
  let outputs = Component$ReventlessCore.outputs(dcbEventLogUnknown);
678
688
  let tableResource = outputs.resources[0];
679
689
  PluginRuntime_Builder$ReventlessAws.registerDcbTableName(tableResource.name);
@@ -742,7 +752,7 @@ function MakeWithConfig(Config) {
742
752
  })(ClonerRunner_Fargate$ReventlessAws)(PluginRuntime_Builder$ReventlessAws.Make({
743
753
  make: EventCollectorChannel_SQS$ReventlessAws.make,
744
754
  connect: EventCollectorChannel_SQS$ReventlessAws.connect
745
- }))(DcbEventLogStorage_DynamoDb$ReventlessAws)(EventTopicPublisher_DynamoDbStream$ReventlessAws)({
755
+ }))(DcbEventLogStorage$ReventlessAws.Selectable)(EventTopicPublisher_DynamoDbStream$ReventlessAws)({
746
756
  make: CommandTopicChannel_SQS_Sync$ReventlessAws.make
747
757
  })({
748
758
  make: CommandTopicChannel_SQS_Async$ReventlessAws.make
@@ -805,6 +815,32 @@ function MakeWithConfig(Config) {
805
815
  hooks_schedulerRoleUrn.contents = outputs.resource.urn;
806
816
  return Component$ReventlessCore.operations(component);
807
817
  };
818
+ let provisionPgChangeFeedRelay = () => {
819
+ let match = DcbBackend$ReventlessAws.get();
820
+ if (match === undefined) {
821
+ return;
822
+ }
823
+ let connectionConfig = match.connectionConfig;
824
+ let relayLogs = Stdlib_Array.filterMap(DcbBackend$ReventlessAws.getRelayLogs(), entry => {
825
+ let match = entry.collectorQueueUrl;
826
+ let match$1 = entry.collectorQueueArn;
827
+ if (match !== undefined && match$1 !== undefined) {
828
+ return {
829
+ connectionConfig: connectionConfig,
830
+ logName: entry.logName,
831
+ subscriber: "aws-eventcollector-relay",
832
+ partitionTag: entry.partitionTag,
833
+ targetQueueUrl: match,
834
+ targetQueueArn: match$1
835
+ };
836
+ }
837
+ log.warn("Platform", undefined, `PgChangeFeedRelay: DCB log ` + entry.logName + ` has no EventCollector queue — skipping (its events will not propagate)`);
838
+ });
839
+ if (relayLogs.length !== 0) {
840
+ PgChangeFeedRelay_Builder$ReventlessAws.make("PgChangeFeedRelay", relayLogs, match.securityGroupId, match.subnetIds, undefined, undefined);
841
+ return;
842
+ }
843
+ };
808
844
  let makePlatform = (version, plugins) => {
809
845
  log.info("Platform", undefined, `v` + version);
810
846
  let scheduler = makeScheduler();
@@ -856,6 +892,7 @@ function MakeWithConfig(Config) {
856
892
  if (pluginComponent !== undefined) {
857
893
  Plugin_Helpers$ReventlessCore.exportPluginOutputs(Component$ReventlessCore.outputs(Primitive_option.valFromOption(pluginComponent)));
858
894
  }
895
+ provisionPgChangeFeedRelay();
859
896
  if (Config.splitApi) {
860
897
  Pulumi$Pulumi.$$export("platformApiId", Output$Pulumi.flatMap(platformApi, api => api.id));
861
898
  Pulumi$Pulumi.$$export("platformApiEndpoint", Output$Pulumi.flatMap(platformApi, api => api.uris.apply(uris => uris.GRAPHQL)));
@@ -920,7 +957,7 @@ function MakeWithConfig(Config) {
920
957
  return;
921
958
  }
922
959
  });
923
- let baseFragment = Config.splitApi ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin");
960
+ let baseFragment = Config.splitApi ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin", undefined);
924
961
  let sdl = GraphQL_Stitcher$ReventlessCore.stitch(baseFragment, fragments);
925
962
  return await AppSync_Adapter$ReventlessAws.startSchemaCreationRetrying(AppSync_Adapter$ReventlessAws.getClient(), {
926
963
  apiId: apiId,
@@ -1129,6 +1166,7 @@ function MakeWithConfig(Config) {
1129
1166
  Pulumi$Pulumi.$$export("_interopMeta", Plugin_Helpers$ReventlessCore.getInteropMeta());
1130
1167
  let pluginOutputs = Component$ReventlessCore.outputs(pluginComponent);
1131
1168
  Plugin_Helpers$ReventlessCore.exportPluginOutputs(pluginOutputs);
1169
+ provisionPgChangeFeedRelay();
1132
1170
  let hbConfig = PluginRuntime_Builder$ReventlessAws.heartbeatConfigRef.contents;
1133
1171
  let match = IndexJs.isDryRun();
1134
1172
  let match$1 = hbConfig.epQueueUrl;
@@ -1207,6 +1245,11 @@ function Make($star) {
1207
1245
  Stdlib_Option.forEach(param.async, AggregateRuntime_Builder_Single_Async$ReventlessAws.setConfig);
1208
1246
  });
1209
1247
  Stdlib_Option.forEach(commandHandlerConfig.stateChanges, param => PluginRuntime_Builder$ReventlessAws.setStateChangesConfig(param.sync, param.async, undefined));
1248
+ Stdlib_Option.forEach(undefined, pg => DcbBackend$ReventlessAws.set({
1249
+ connectionConfig: pg.connectionConfig,
1250
+ securityGroupId: pg.securityGroupId,
1251
+ subnetIds: pg.subnetIds
1252
+ }));
1210
1253
  let currentDeployTarget = {
1211
1254
  contents: "Domain"
1212
1255
  };
@@ -1686,7 +1729,7 @@ function Make($star) {
1686
1729
  return Output$Pulumi.flatMap(targetApi, api => Output$Pulumi.flatMap(api.id, apiId => {
1687
1730
  let runSchemaPush = () => writeAndScanFragments().then(async allPluginFragments => {
1688
1731
  let baseFragment;
1689
- baseFragment = capturedDeployTarget === "Domain" ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin");
1732
+ baseFragment = capturedDeployTarget === "Domain" ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin", undefined);
1690
1733
  let sdl = GraphQL_Stitcher$ReventlessCore.stitch(baseFragment, allPluginFragments);
1691
1734
  let currentHash = AppSync_Adapter$ReventlessAws.sha256Hex(sdl);
1692
1735
  let storedHash = tableNameOpt !== undefined ? await readSchemaHash(tableNameOpt, apiId) : undefined;
@@ -1741,7 +1784,7 @@ function Make($star) {
1741
1784
  let hooks_preAdminResolversSchemaHook = adminBarrier => {
1742
1785
  let match = splitApiOutputsRef.contents;
1743
1786
  let targetApi = match !== undefined ? match.platformApi : domainApi;
1744
- let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin");
1787
+ let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin", undefined);
1745
1788
  let sdl = GraphQL_Stitcher$ReventlessCore.stitch(adminBaseFragment, []);
1746
1789
  return Output$Pulumi.flatMap(Pulumi.all([
1747
1790
  targetApi,
@@ -1762,6 +1805,9 @@ function Make($star) {
1762
1805
  let hooks_inboundAppSyncResolverHook = param => InboundTranslationResolvers_AppSync$ReventlessAws.make(resolveHookedApi(), param.runtime, param.fieldNames, param.opts);
1763
1806
  let hooks_dcbAppSyncResolverHook = param => CommandGeneratorResolvers_AppSync$ReventlessAws.makeDcb(resolveHookedApi(), param.runtime, param.fieldNames, param.tags, param.opts);
1764
1807
  let hooks_onDcbEventLogCreated = dcbEventLogUnknown => {
1808
+ if (DcbBackend$ReventlessAws.isPostgres()) {
1809
+ return;
1810
+ }
1765
1811
  let outputs = Component$ReventlessCore.outputs(dcbEventLogUnknown);
1766
1812
  let tableResource = outputs.resources[0];
1767
1813
  PluginRuntime_Builder$ReventlessAws.registerDcbTableName(tableResource.name);
@@ -1830,7 +1876,7 @@ function Make($star) {
1830
1876
  })(ClonerRunner_Fargate$ReventlessAws)(PluginRuntime_Builder$ReventlessAws.Make({
1831
1877
  make: EventCollectorChannel_SQS$ReventlessAws.make,
1832
1878
  connect: EventCollectorChannel_SQS$ReventlessAws.connect
1833
- }))(DcbEventLogStorage_DynamoDb$ReventlessAws)(EventTopicPublisher_DynamoDbStream$ReventlessAws)({
1879
+ }))(DcbEventLogStorage$ReventlessAws.Selectable)(EventTopicPublisher_DynamoDbStream$ReventlessAws)({
1834
1880
  make: CommandTopicChannel_SQS_Sync$ReventlessAws.make
1835
1881
  })({
1836
1882
  make: CommandTopicChannel_SQS_Async$ReventlessAws.make
@@ -1893,6 +1939,32 @@ function Make($star) {
1893
1939
  hooks_schedulerRoleUrn.contents = outputs.resource.urn;
1894
1940
  return Component$ReventlessCore.operations(component);
1895
1941
  };
1942
+ let provisionPgChangeFeedRelay = () => {
1943
+ let match = DcbBackend$ReventlessAws.get();
1944
+ if (match === undefined) {
1945
+ return;
1946
+ }
1947
+ let connectionConfig = match.connectionConfig;
1948
+ let relayLogs = Stdlib_Array.filterMap(DcbBackend$ReventlessAws.getRelayLogs(), entry => {
1949
+ let match = entry.collectorQueueUrl;
1950
+ let match$1 = entry.collectorQueueArn;
1951
+ if (match !== undefined && match$1 !== undefined) {
1952
+ return {
1953
+ connectionConfig: connectionConfig,
1954
+ logName: entry.logName,
1955
+ subscriber: "aws-eventcollector-relay",
1956
+ partitionTag: entry.partitionTag,
1957
+ targetQueueUrl: match,
1958
+ targetQueueArn: match$1
1959
+ };
1960
+ }
1961
+ log.warn("Platform", undefined, `PgChangeFeedRelay: DCB log ` + entry.logName + ` has no EventCollector queue — skipping (its events will not propagate)`);
1962
+ });
1963
+ if (relayLogs.length !== 0) {
1964
+ PgChangeFeedRelay_Builder$ReventlessAws.make("PgChangeFeedRelay", relayLogs, match.securityGroupId, match.subnetIds, undefined, undefined);
1965
+ return;
1966
+ }
1967
+ };
1896
1968
  let makePlatform = (version, plugins) => {
1897
1969
  log.info("Platform", undefined, `v` + version);
1898
1970
  let scheduler = makeScheduler();
@@ -1939,6 +2011,7 @@ function Make($star) {
1939
2011
  if (pluginComponent !== undefined) {
1940
2012
  Plugin_Helpers$ReventlessCore.exportPluginOutputs(Component$ReventlessCore.outputs(Primitive_option.valFromOption(pluginComponent)));
1941
2013
  }
2014
+ provisionPgChangeFeedRelay();
1942
2015
  Pulumi$Pulumi.$$export("platformApiId", Output$Pulumi.flatMap(platformApi, api => api.id));
1943
2016
  Pulumi$Pulumi.$$export("platformApiEndpoint", Output$Pulumi.flatMap(platformApi, api => api.uris.apply(uris => uris.GRAPHQL)));
1944
2017
  Pulumi$Pulumi.$$export("platformApiRoleArn", Output$Pulumi.flatMap(platformApiRole, role => role.arn));
@@ -2194,6 +2267,7 @@ function Make($star) {
2194
2267
  Pulumi$Pulumi.$$export("_interopMeta", Plugin_Helpers$ReventlessCore.getInteropMeta());
2195
2268
  let pluginOutputs = Component$ReventlessCore.outputs(pluginComponent);
2196
2269
  Plugin_Helpers$ReventlessCore.exportPluginOutputs(pluginOutputs);
2270
+ provisionPgChangeFeedRelay();
2197
2271
  let hbConfig = PluginRuntime_Builder$ReventlessAws.heartbeatConfigRef.contents;
2198
2272
  let match = IndexJs.isDryRun();
2199
2273
  let match$1 = hbConfig.epQueueUrl;
@@ -52,7 +52,7 @@ function make(name, referencesName, referencesDb, countsName, countsDb, param, s
52
52
  let componentOpts = {
53
53
  parent: componentOpts_parent
54
54
  };
55
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, code, sourceCodeHash, envVars, 1024, 30, undefined, undefined, undefined, undefined, componentOpts);
55
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, code, sourceCodeHash, envVars, 1024, 30, undefined, undefined, undefined, undefined, undefined, componentOpts);
56
56
  let lambda = runtime.parts.lambda;
57
57
  let subscribe = (sourceName, source) => Util_EventSourceMapping$ReventlessAws.subscribe(undefined, lambda, name, sourceName, source, opts);
58
58
  subscribe(referencesName, referencesStream);
@@ -0,0 +1,78 @@
1
+ // AWS-side selection for the DCB EventLog storage backend (B2.3c platform toggle).
2
+ //
3
+ // When `Platform.MakeWithConfig` is given a `~pgConnection`, it records the resolved
4
+ // selection here BEFORE any admin/plugin `construct` runs. Read by:
5
+ // - `DcbEventLogStorage.Selectable` — route DCB storage to Postgres (no DynamoDB
6
+ // table/stream) and bind the Postgres operation set;
7
+ // - the DCB-command runtime env builder — inject `PG_CONNECTION` so the B2.1
8
+ // `DcbCommandTopicEntryPoint.mjs` Postgres branch activates;
9
+ // - `Platform.makePlatform` — provision the `PgChangeFeedRelay` and feed the plugin
10
+ // EventCollector SQS queue.
11
+ //
12
+ // All DCB logs in the platform share one selection (the confirmed B2.3c toggle);
13
+ // aggregate EventLogs are unaffected — they stay on DynamoDB until the classic
14
+ // `event_log` change feed lands (B2.5). See docs/plans/aws-postgres-change-feed-bridge.md.
15
+
16
+ type selection = {
17
+ connectionConfig: Pulumi.Output.t<PgConnection.connectionConfig>,
18
+ /** DB-access security group every DCB/relay Lambda attaches (PgConnection.securityGroupId). */
19
+ securityGroupId: Pulumi.Output.t<string>,
20
+ /** Private subnets for the in-VPC DCB/relay Lambdas (PgConnection.subnetIds). */
21
+ subnetIds: array<Pulumi.Input.t<string>>,
22
+ }
23
+
24
+ let selectionRef: ref<option<selection>> = ref(None)
25
+
26
+ /** Record the DCB Postgres selection. Called once by the platform before construct. */
27
+ let set = (sel: selection) => selectionRef := Some(sel)
28
+
29
+ /** The active DCB Postgres selection, if the platform was given a `PgConnection`. */
30
+ let get = (): option<selection> => selectionRef.contents
31
+
32
+ /** Whether DCB storage is Postgres-backed on this platform. */
33
+ let isPostgres = (): bool => selectionRef.contents->Option.isSome
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Relay-log registry (B2.3d)
37
+ //
38
+ // One entry per Postgres-backed DCB log, assembled across the plugin build in two
39
+ // steps so `makePlatform` can provision the change-feed relay from complete inputs:
40
+ // 1. `DcbEventLogStorage_Postgres.make` registers `{logName, partitionTag}` when
41
+ // the DCB storage is created (both are known there, AWS-side).
42
+ // 2. `PluginRuntime_Builder.forPluginEventCollector` attaches that plugin's
43
+ // EventCollector SQS queue, keyed by the canonical `logName`
44
+ // (`<plugin>DcbEventLog`), when the collector Lambda is built.
45
+ // Both run during the plugin's own `P.make()`, so every registered log ends the
46
+ // build with its collector queue attached. `makePlatform` then reads this and calls
47
+ // `PgChangeFeedRelay_Builder.make` once (shared relay, per-log checkpoints).
48
+ // ---------------------------------------------------------------------------
49
+ type relayLogEntry = {
50
+ logName: string,
51
+ partitionTag: Reventless.DcbTag.derivedPartitionTag,
52
+ mutable collectorQueueUrl: option<Pulumi.Output.t<string>>,
53
+ mutable collectorQueueArn: option<Pulumi.Output.t<string>>,
54
+ }
55
+
56
+ let relayLogs: array<relayLogEntry> = []
57
+
58
+ /** Step 1: record a Postgres DCB log and its partition tag (from the storage maker). */
59
+ let registerRelayLog = (~logName: string, ~partitionTag: Reventless.DcbTag.derivedPartitionTag) =>
60
+ relayLogs
61
+ ->Array.push({logName, partitionTag, collectorQueueUrl: None, collectorQueueArn: None})
62
+ ->ignore
63
+
64
+ /** Step 2: attach a plugin's EventCollector SQS queue to its DCB log's relay entry. */
65
+ let attachCollectorQueue = (
66
+ ~logName: string,
67
+ ~url: Pulumi.Output.t<string>,
68
+ ~arn: Pulumi.Output.t<string>,
69
+ ) =>
70
+ relayLogs
71
+ ->Array.find(e => e.logName == logName)
72
+ ->Option.forEach(e => {
73
+ e.collectorQueueUrl = Some(url)
74
+ e.collectorQueueArn = Some(arn)
75
+ })
76
+
77
+ /** All registered relay logs (read by `makePlatform` to provision the relay). */
78
+ let getRelayLogs = (): array<relayLogEntry> => relayLogs
@@ -0,0 +1,53 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+
5
+ let selectionRef = {
6
+ contents: undefined
7
+ };
8
+
9
+ function set(sel) {
10
+ selectionRef.contents = sel;
11
+ }
12
+
13
+ function get() {
14
+ return selectionRef.contents;
15
+ }
16
+
17
+ function isPostgres() {
18
+ return Stdlib_Option.isSome(selectionRef.contents);
19
+ }
20
+
21
+ let relayLogs = [];
22
+
23
+ function registerRelayLog(logName, partitionTag) {
24
+ relayLogs.push({
25
+ logName: logName,
26
+ partitionTag: partitionTag,
27
+ collectorQueueUrl: undefined,
28
+ collectorQueueArn: undefined
29
+ });
30
+ }
31
+
32
+ function attachCollectorQueue(logName, url, arn) {
33
+ Stdlib_Option.forEach(relayLogs.find(e => e.logName === logName), e => {
34
+ e.collectorQueueUrl = url;
35
+ e.collectorQueueArn = arn;
36
+ });
37
+ }
38
+
39
+ function getRelayLogs() {
40
+ return relayLogs;
41
+ }
42
+
43
+ export {
44
+ selectionRef,
45
+ set,
46
+ get,
47
+ isPostgres,
48
+ relayLogs,
49
+ registerRelayLog,
50
+ attachCollectorQueue,
51
+ getRelayLogs,
52
+ }
53
+ /* No side effect */
@@ -1 +1,21 @@
1
1
  module DynamoDb = DcbEventLogStorage_DynamoDb
2
+ module Postgres = DcbEventLogStorage_Postgres
3
+
4
+ // Build-time DCB storage selector (B2.3c). Reads `DcbBackend` — set by the platform
5
+ // when a `PgConnection` is supplied — and dispatches to Postgres (no table/stream) or
6
+ // DynamoDB. Passed to the Plugin / Admin functors in place of `.DynamoDb` so every DCB
7
+ // log follows the platform toggle, while aggregate EventLogs stay on DynamoDB.
8
+ module Selectable = {
9
+ let make: ReventlessCore.DcbEventLog_Adapter.storageMaker = (
10
+ ~name,
11
+ ~indexes,
12
+ ~partitionTag,
13
+ ~crossPartitionTagKeys=[],
14
+ ~opts,
15
+ ) =>
16
+ if DcbBackend.isPostgres() {
17
+ Postgres.make(~name, ~indexes, ~partitionTag, ~crossPartitionTagKeys, ~opts)
18
+ } else {
19
+ DynamoDb.make(~name, ~indexes, ~partitionTag, ~crossPartitionTagKeys, ~opts)
20
+ }
21
+ }
@@ -1,9 +1,29 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as DcbBackend$ReventlessAws from "./DcbBackend.res.mjs";
4
+ import * as DcbEventLogStorage_DynamoDb$ReventlessAws from "./DcbEventLogStorage_DynamoDb.res.mjs";
5
+ import * as DcbEventLogStorage_Postgres$ReventlessAws from "./DcbEventLogStorage_Postgres.res.mjs";
6
+
7
+ function make(name, indexes, partitionTag, crossPartitionTagKeysOpt, opts) {
8
+ let crossPartitionTagKeys = crossPartitionTagKeysOpt !== undefined ? crossPartitionTagKeysOpt : [];
9
+ if (DcbBackend$ReventlessAws.isPostgres()) {
10
+ return DcbEventLogStorage_Postgres$ReventlessAws.make(name, indexes, partitionTag, crossPartitionTagKeys, opts);
11
+ } else {
12
+ return DcbEventLogStorage_DynamoDb$ReventlessAws.make(name, indexes, partitionTag, crossPartitionTagKeys, opts);
13
+ }
14
+ }
15
+
16
+ let Selectable = {
17
+ make: make
18
+ };
3
19
 
4
20
  let DynamoDb;
5
21
 
22
+ let Postgres;
23
+
6
24
  export {
7
25
  DynamoDb,
26
+ Postgres,
27
+ Selectable,
8
28
  }
9
- /* No side effect */
29
+ /* DcbEventLogStorage_DynamoDb-ReventlessAws Not a pure module */