@reventlessdev/reventless-aws 3.0.0-alpha.172 → 3.0.0-alpha.173
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 +11 -0
- package/package.json +6 -6
- package/src/Platform.res.mjs +5 -5
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +1 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres_Runtime.res +26 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres_Runtime.res.mjs +15 -0
- package/src/adapter/EventLog/EventLogStorage_Postgres_Runtime.res +23 -0
- package/src/adapter/EventLog/EventLogStorage_Postgres_Runtime.res.mjs +14 -0
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +181 -0
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +135 -0
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +64 -0
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +36 -0
- package/src/adapter/Runtime/AggregateEntryPoint.mjs +27 -9
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res.mjs +3 -3
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res.mjs +3 -3
- package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res.mjs +1 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res.mjs +1 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +1 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +1 -1
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +1 -1
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +20 -6
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res.mjs +1 -1
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +1 -1
- package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res.mjs +1 -1
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +52 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +38 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +20 -2
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +1 -1
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +1 -1
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +1 -1
- package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +1 -1
- package/src/components/Api/AppSync_Adapter.res +72 -8
- package/src/components/Api/AppSync_Adapter.res.mjs +45 -6
- package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +1 -1
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +2 -2
- package/tests/AppSync_AdapterTest.res +140 -0
- package/tests/AppSync_AdapterTest.res.mjs +182 -6
- package/tests/PgChangeFeedRelay_RuntimeTest.res +44 -0
- package/tests/PgChangeFeedRelay_RuntimeTest.res.mjs +70 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.173 (2026-07-05)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **reventless-aws:** aggregate EventLog Postgres runtime + entry-point branch ([fd81ece](https://github.com/ReventlessDev/reventless-core/commit/fd81ece65c2bf6aebf227d74ec2d0f002833d351))
|
|
11
|
+
* **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)
|
|
12
|
+
* **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))
|
|
13
|
+
* **reventless-aws:** Postgres change-feed relay deploy builder + Lambda VPC (B2.3a/b, C1) ([64d67f3](https://github.com/ReventlessDev/reventless-core/commit/64d67f3ecd2c42d59ba814d6d821683ae8c81612))
|
|
14
|
+
* **reventless-aws:** Postgres change-feed relay runtime (B2.2) ([9ccb601](https://github.com/ReventlessDev/reventless-core/commit/9ccb60192457e9da21869e0d1c98fe8dbfb42178))
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
# 3.0.0-alpha.172 (2026-07-05)
|
|
7
18
|
|
|
8
19
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.173",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
"sury": "11.0.0-alpha.4",
|
|
10
10
|
"uuid": "^13.0.0",
|
|
11
11
|
"@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.20",
|
|
12
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.24",
|
|
13
12
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.5",
|
|
13
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.24",
|
|
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.
|
|
17
|
-
"@reventlessdev/
|
|
18
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.85",
|
|
19
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.1",
|
|
17
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.140",
|
|
18
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.86",
|
|
20
19
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.23",
|
|
20
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.2",
|
|
21
21
|
"@reventlessdev/reventless-spec": "3.0.0-alpha.64"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
package/src/Platform.res.mjs
CHANGED
|
@@ -598,7 +598,7 @@ function MakeWithConfig(Config) {
|
|
|
598
598
|
return Output$Pulumi.flatMap(targetApi, api => Output$Pulumi.flatMap(api.id, apiId => {
|
|
599
599
|
let runSchemaPush = () => writeAndScanFragments().then(async allPluginFragments => {
|
|
600
600
|
let baseFragment;
|
|
601
|
-
baseFragment = capturedDeployTarget === "Domain" && Config.splitApi ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin");
|
|
601
|
+
baseFragment = capturedDeployTarget === "Domain" && Config.splitApi ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin", undefined);
|
|
602
602
|
let sdl = GraphQL_Stitcher$ReventlessCore.stitch(baseFragment, allPluginFragments);
|
|
603
603
|
let currentHash = AppSync_Adapter$ReventlessAws.sha256Hex(sdl);
|
|
604
604
|
let storedHash = tableNameOpt !== undefined ? await readSchemaHash(tableNameOpt, apiId) : undefined;
|
|
@@ -653,7 +653,7 @@ function MakeWithConfig(Config) {
|
|
|
653
653
|
let hooks_preAdminResolversSchemaHook = adminBarrier => {
|
|
654
654
|
let match = splitApiOutputsRef.contents;
|
|
655
655
|
let targetApi = match !== undefined ? match.platformApi : domainApi;
|
|
656
|
-
let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin");
|
|
656
|
+
let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin", undefined);
|
|
657
657
|
let sdl = GraphQL_Stitcher$ReventlessCore.stitch(adminBaseFragment, []);
|
|
658
658
|
return Output$Pulumi.flatMap(Pulumi.all([
|
|
659
659
|
targetApi,
|
|
@@ -920,7 +920,7 @@ function MakeWithConfig(Config) {
|
|
|
920
920
|
return;
|
|
921
921
|
}
|
|
922
922
|
});
|
|
923
|
-
let baseFragment = Config.splitApi ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin");
|
|
923
|
+
let baseFragment = Config.splitApi ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin", undefined);
|
|
924
924
|
let sdl = GraphQL_Stitcher$ReventlessCore.stitch(baseFragment, fragments);
|
|
925
925
|
return await AppSync_Adapter$ReventlessAws.startSchemaCreationRetrying(AppSync_Adapter$ReventlessAws.getClient(), {
|
|
926
926
|
apiId: apiId,
|
|
@@ -1686,7 +1686,7 @@ function Make($star) {
|
|
|
1686
1686
|
return Output$Pulumi.flatMap(targetApi, api => Output$Pulumi.flatMap(api.id, apiId => {
|
|
1687
1687
|
let runSchemaPush = () => writeAndScanFragments().then(async allPluginFragments => {
|
|
1688
1688
|
let baseFragment;
|
|
1689
|
-
baseFragment = capturedDeployTarget === "Domain" ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin");
|
|
1689
|
+
baseFragment = capturedDeployTarget === "Domain" ? emptyBaseFragment : AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin", undefined);
|
|
1690
1690
|
let sdl = GraphQL_Stitcher$ReventlessCore.stitch(baseFragment, allPluginFragments);
|
|
1691
1691
|
let currentHash = AppSync_Adapter$ReventlessAws.sha256Hex(sdl);
|
|
1692
1692
|
let storedHash = tableNameOpt !== undefined ? await readSchemaHash(tableNameOpt, apiId) : undefined;
|
|
@@ -1741,7 +1741,7 @@ function Make($star) {
|
|
|
1741
1741
|
let hooks_preAdminResolversSchemaHook = adminBarrier => {
|
|
1742
1742
|
let match = splitApiOutputsRef.contents;
|
|
1743
1743
|
let targetApi = match !== undefined ? match.platformApi : domainApi;
|
|
1744
|
-
let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin");
|
|
1744
|
+
let adminBaseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin", undefined);
|
|
1745
1745
|
let sdl = GraphQL_Stitcher$ReventlessCore.stitch(adminBaseFragment, []);
|
|
1746
1746
|
return Output$Pulumi.flatMap(Pulumi.all([
|
|
1747
1747
|
targetApi,
|
|
@@ -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,26 @@
|
|
|
1
|
+
// AWS-side runtime DcbEventLog operations for the Postgres backend. The DCB
|
|
2
|
+
// analogue of EventLogStorage_Postgres_Runtime: turn a resolved connection config
|
|
3
|
+
// into the DCB operation set (`read`/`append`/`readStream`), bound to the
|
|
4
|
+
// container-lifetime pool from PgRuntime. Consumed by DcbCommandTopicEntryPoint.mjs
|
|
5
|
+
// (Postgres branch, selected when a PG_CONNECTION is present in HANDLER_CONFIG).
|
|
6
|
+
//
|
|
7
|
+
// The pg maker ignores `~indexes` / `~partitionTag` / `~crossPartitionTagKeys`
|
|
8
|
+
// (Postgres evaluates the real `DcbTag.query` atomically — no GSI routing), so
|
|
9
|
+
// they are passed empty here. `logName` is the `dcb_event.log_name` discriminator.
|
|
10
|
+
|
|
11
|
+
let opsFor = (
|
|
12
|
+
config: PgConnection.connectionConfig,
|
|
13
|
+
~logName: string,
|
|
14
|
+
~lockStrategy: ReventlessPostgres.DcbEventLogStorage_Postgres.lockStrategy=#AdvisoryLocks,
|
|
15
|
+
): ReventlessCore.DcbEventLog_Adapter.operations => {
|
|
16
|
+
let pool = PgRuntime.poolFor(config)
|
|
17
|
+
let (_name, ops, _storage) = ReventlessPostgres.DcbEventLogStorage_Postgres.makeStorage(
|
|
18
|
+
~pool,
|
|
19
|
+
~name=logName,
|
|
20
|
+
~indexes=[],
|
|
21
|
+
~partitionTag=(),
|
|
22
|
+
~opts=(),
|
|
23
|
+
~lockStrategy,
|
|
24
|
+
)
|
|
25
|
+
ops
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as PgRuntime$ReventlessAws from "../Postgres/PgRuntime.res.mjs";
|
|
4
|
+
import * as DcbEventLogStorage_Postgres$ReventlessPostgres from "@reventlessdev/reventless-postgres/src/DcbEventLogStorage_Postgres.res.mjs";
|
|
5
|
+
|
|
6
|
+
function opsFor(config, logName, lockStrategyOpt) {
|
|
7
|
+
let lockStrategy = lockStrategyOpt !== undefined ? lockStrategyOpt : "AdvisoryLocks";
|
|
8
|
+
let pool = PgRuntime$ReventlessAws.poolFor(config);
|
|
9
|
+
return DcbEventLogStorage_Postgres$ReventlessPostgres.makeStorage(pool, logName, [], undefined, undefined, undefined, lockStrategy, undefined)[1];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
opsFor,
|
|
14
|
+
}
|
|
15
|
+
/* PgRuntime-ReventlessAws Not a pure module */
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// AWS-side runtime EventLog operations for the Postgres backend. Mirrors the
|
|
2
|
+
// role of EventLogStorage_DynamoDb_Runtime: turn a resolved connection config
|
|
3
|
+
// into the classic EventLog operation set, bound to the container-lifetime pool
|
|
4
|
+
// from PgRuntime. Consumed by AggregateEntryPoint.mjs (Postgres branch, selected
|
|
5
|
+
// when a PG_CONNECTION is present in HANDLER_CONFIG).
|
|
6
|
+
//
|
|
7
|
+
// Unlike DynamoDB (table-per-aggregate), all aggregates share the single
|
|
8
|
+
// Postgres `event_log` table, discriminated by `log_name`. `logName` is that
|
|
9
|
+
// per-aggregate discriminator — the same stable identifier the builder would
|
|
10
|
+
// otherwise use as the DynamoDB table name.
|
|
11
|
+
|
|
12
|
+
let opsFor = (
|
|
13
|
+
config: PgConnection.connectionConfig,
|
|
14
|
+
~logName: string,
|
|
15
|
+
): ReventlessCore.EventLog_Adapter.operations => {
|
|
16
|
+
let pool = PgRuntime.poolFor(config)
|
|
17
|
+
let (_name, ops, _storage) = ReventlessPostgres.EventLogStorage_Postgres.makeStorage(
|
|
18
|
+
~pool,
|
|
19
|
+
~name=logName,
|
|
20
|
+
~opts=(),
|
|
21
|
+
)
|
|
22
|
+
ops
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as PgRuntime$ReventlessAws from "../Postgres/PgRuntime.res.mjs";
|
|
4
|
+
import * as EventLogStorage_Postgres$ReventlessPostgres from "@reventlessdev/reventless-postgres/src/EventLogStorage_Postgres.res.mjs";
|
|
5
|
+
|
|
6
|
+
function opsFor(config, logName) {
|
|
7
|
+
let pool = PgRuntime$ReventlessAws.poolFor(config);
|
|
8
|
+
return EventLogStorage_Postgres$ReventlessPostgres.makeStorage(pool, logName, undefined, undefined)[1];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
opsFor,
|
|
13
|
+
}
|
|
14
|
+
/* PgRuntime-ReventlessAws Not a pure module */
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// Deploy-time builder for the Postgres change-feed relay (B2.3b).
|
|
2
|
+
//
|
|
3
|
+
// Provisions a scheduled, in-VPC Lambda that drains each Postgres DCB log via
|
|
4
|
+
// PgChangeFeed and relays events to the plugin EventCollector SQS queue — which
|
|
5
|
+
// fans out to read-model projections, aggregate command topics, and the
|
|
6
|
+
// cross-plugin SNS EventTopic. The platform (D1, B2.3c) calls this with the set of
|
|
7
|
+
// Postgres-backed DCB logs and points the EventCollector's event source at the
|
|
8
|
+
// relay-fed queue.
|
|
9
|
+
//
|
|
10
|
+
// NOTE: EventBridge rate rules have a 1-minute floor, so v1 poll latency is
|
|
11
|
+
// >= 1 minute. Sub-minute latency needs a self-invoking loop / EventBridge
|
|
12
|
+
// Scheduler (follow-up) or the Fargate-LISTEN upgrade.
|
|
13
|
+
|
|
14
|
+
// One Postgres-backed DCB log to relay.
|
|
15
|
+
type relayLog = {
|
|
16
|
+
connectionConfig: Pulumi.Output.t<PgConnection.connectionConfig>,
|
|
17
|
+
/** dcb_event.log_name — also the DCB `event_log` discriminator. */
|
|
18
|
+
logName: string,
|
|
19
|
+
/** dcb_subscription checkpoint key for this relay. */
|
|
20
|
+
subscriber: string,
|
|
21
|
+
/** Plugin EventCollector SQS queue this log's events are relayed to. */
|
|
22
|
+
targetQueueUrl: Pulumi.Output.t<string>,
|
|
23
|
+
targetQueueArn: Pulumi.Output.t<string>,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ~securityGroupId: DB-access SG the relay Lambda attaches (PgConnection.securityGroupId).
|
|
27
|
+
// ~subnetIds: private subnets for the relay Lambda (PgConnection.subnetIds).
|
|
28
|
+
let make = (
|
|
29
|
+
~name: string,
|
|
30
|
+
~logs: array<relayLog>,
|
|
31
|
+
~securityGroupId: Pulumi.Output.t<string>,
|
|
32
|
+
~subnetIds: array<Pulumi.Input.t<string>>,
|
|
33
|
+
~intervalMinutes: int=1,
|
|
34
|
+
~opts=?,
|
|
35
|
+
): array<ReventlessInfra.Adapter.resource> => {
|
|
36
|
+
open PulumiAws
|
|
37
|
+
let customOpts =
|
|
38
|
+
opts->Option.map(ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions)
|
|
39
|
+
|
|
40
|
+
let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
41
|
+
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs",
|
|
42
|
+
~packageDirs=Dict.make(),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
// HANDLER_CONFIG.logs[], serialized from the deploy-time-resolved Outputs.
|
|
46
|
+
let logJsonOutputs = logs->Array.map(l =>
|
|
47
|
+
(l.connectionConfig, l.targetQueueUrl)
|
|
48
|
+
->Pulumi.Output.all2
|
|
49
|
+
->Pulumi.Output.apply(((cc, queueUrl)) => {
|
|
50
|
+
let pgConnectionJson =
|
|
51
|
+
[
|
|
52
|
+
("host", cc.host->JSON.Encode.string),
|
|
53
|
+
("port", cc.port->Int.toFloat->JSON.Encode.float),
|
|
54
|
+
("database", cc.database->JSON.Encode.string),
|
|
55
|
+
("username", cc.username->JSON.Encode.string),
|
|
56
|
+
("secretArn", cc.secretArn->JSON.Encode.string),
|
|
57
|
+
]
|
|
58
|
+
->Dict.fromArray
|
|
59
|
+
->JSON.Encode.object
|
|
60
|
+
[
|
|
61
|
+
("pgConnection", pgConnectionJson),
|
|
62
|
+
("logName", l.logName->JSON.Encode.string),
|
|
63
|
+
("subscriber", l.subscriber->JSON.Encode.string),
|
|
64
|
+
("targetQueueUrl", queueUrl->JSON.Encode.string),
|
|
65
|
+
]
|
|
66
|
+
->Dict.fromArray
|
|
67
|
+
->JSON.Encode.object
|
|
68
|
+
->JSON.stringify
|
|
69
|
+
})
|
|
70
|
+
)
|
|
71
|
+
let handlerConfigOutput =
|
|
72
|
+
Pulumi.Output.all(logJsonOutputs)->Pulumi.Output.apply(items =>
|
|
73
|
+
`{"logs":[${items->Array.join(",")}]}`
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
let envVars = Dict.fromArray([("HANDLER_CONFIG", handlerConfigOutput->Pulumi.Output.asInput)])
|
|
77
|
+
|
|
78
|
+
let vpcConfig =
|
|
79
|
+
securityGroupId
|
|
80
|
+
->Pulumi.Output.apply(sgId =>
|
|
81
|
+
(
|
|
82
|
+
{
|
|
83
|
+
Lambda.Function.subnetIds: subnetIds->Pulumi.Input.make,
|
|
84
|
+
securityGroupIds: [sgId->Pulumi.Input.make]->Pulumi.Input.make,
|
|
85
|
+
}: Lambda.Function.vpcConfig
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
->Pulumi.Output.asInput
|
|
89
|
+
|
|
90
|
+
let runtime = RuntimeEnvironment_Lambda.makeFromCodeAsset(
|
|
91
|
+
~name,
|
|
92
|
+
~code,
|
|
93
|
+
~sourceCodeHash,
|
|
94
|
+
~envVars,
|
|
95
|
+
~vpcConfig,
|
|
96
|
+
~opts?,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// IAM: GetSecretValue on each log's DB secret + SendMessage on each target queue.
|
|
100
|
+
let secretArns =
|
|
101
|
+
logs->Array.map(l => l.connectionConfig->Pulumi.Output.apply(cc => cc.secretArn))
|
|
102
|
+
let queueArns = logs->Array.map(l => l.targetQueueArn)
|
|
103
|
+
let _iam =
|
|
104
|
+
(Pulumi.Output.all(secretArns), Pulumi.Output.all(queueArns), runtime.parts.lambdaRole.id)
|
|
105
|
+
->Pulumi.Output.all3
|
|
106
|
+
->Pulumi.Output.apply(((secretArns, queueArns, roleId)) => {
|
|
107
|
+
open PulumiAws.PolicyDocument
|
|
108
|
+
let _ = PulumiAws.IAM.RolePolicy.make(
|
|
109
|
+
~name=`${name}RelayAccess`,
|
|
110
|
+
~args={
|
|
111
|
+
policy: PulumiAws.PolicyDocument.make(
|
|
112
|
+
~id=`${name}RelayAccessPolicy`,
|
|
113
|
+
~statements=[
|
|
114
|
+
{
|
|
115
|
+
sid: "AllowGetSecret",
|
|
116
|
+
effect: Allow,
|
|
117
|
+
actions: Action("secretsmanager:GetSecretValue"),
|
|
118
|
+
resources: Resources(secretArns),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
sid: "AllowSendSqs",
|
|
122
|
+
effect: Allow,
|
|
123
|
+
actions: Action("sqs:SendMessage"),
|
|
124
|
+
resources: Resources(queueArns),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
)
|
|
128
|
+
->PulumiAws.PolicyDocument.toJsonString
|
|
129
|
+
->Pulumi.Input.make,
|
|
130
|
+
role: roleId->Pulumi.Input.make,
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Scheduled poll: EventRule (rate) → Permission → EventTarget.
|
|
136
|
+
let rule = {
|
|
137
|
+
open PulumiAws.Cloudwatch
|
|
138
|
+
EventRule.make(
|
|
139
|
+
~name=`${Pulumi.Pulumi.getStackName()}-${name}`,
|
|
140
|
+
~args={
|
|
141
|
+
description: "Poll Postgres DCB change feed and relay to EventCollector"->Pulumi.Input.make,
|
|
142
|
+
scheduleExpression: EventRule.ScheduleExpression.every(Minutes(intervalMinutes)),
|
|
143
|
+
},
|
|
144
|
+
~opts=?customOpts,
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let lambda = runtime.parts.lambda
|
|
149
|
+
let _wire =
|
|
150
|
+
(lambda->Pulumi.Output.flatMap(l => l.arn), lambda->Pulumi.Output.flatMap(l => l.name))
|
|
151
|
+
->Pulumi.Output.all2
|
|
152
|
+
->Pulumi.Output.apply(((lambdaArn, lambdaName)) => {
|
|
153
|
+
let _permission = PulumiAws.Lambda.Permission.make(
|
|
154
|
+
~name=`${name}SchedulePermission`,
|
|
155
|
+
~args={
|
|
156
|
+
action: "lambda:InvokeFunction",
|
|
157
|
+
function: lambdaName->Pulumi.Input.make,
|
|
158
|
+
principal: AWS.CloudwatchEventRule.principal,
|
|
159
|
+
},
|
|
160
|
+
~opts=?customOpts,
|
|
161
|
+
)
|
|
162
|
+
let _target = {
|
|
163
|
+
open PulumiAws.Cloudwatch
|
|
164
|
+
EventTarget.make(
|
|
165
|
+
~name,
|
|
166
|
+
~args={
|
|
167
|
+
rule: EventTarget.Rule.ofEventRule(rule),
|
|
168
|
+
arn: lambdaArn->Pulumi.Input.make,
|
|
169
|
+
},
|
|
170
|
+
~opts=?customOpts,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
[
|
|
176
|
+
lambda
|
|
177
|
+
->Pulumi.Output.apply(l => l->Util_Lambda.toResource)
|
|
178
|
+
->ReventlessCore.Adapter.outputToResource,
|
|
179
|
+
rule->Util_Cloudwatch.EventRule.toResource,
|
|
180
|
+
]
|
|
181
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Aws from "@pulumi/aws";
|
|
4
|
+
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
5
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Pulumi from "@pulumi/pulumi";
|
|
7
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
8
|
+
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
9
|
+
import * as Adapter$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Adapter.res.mjs";
|
|
10
|
+
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
11
|
+
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
12
|
+
import * as Util_Lambda$ReventlessAws from "../../util/Util_Lambda.res.mjs";
|
|
13
|
+
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
14
|
+
import * as Util_Cloudwatch$ReventlessAws from "../../util/Util_Cloudwatch.res.mjs";
|
|
15
|
+
import * as Cloudwatch_EventRule$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Cloudwatch/Cloudwatch_EventRule.res.mjs";
|
|
16
|
+
import * as Cloudwatch_EventTarget$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Cloudwatch/Cloudwatch_EventTarget.res.mjs";
|
|
17
|
+
import * as RuntimeEnvironment_Lambda$ReventlessAws from "../Runtime/RuntimeEnvironment_Lambda.res.mjs";
|
|
18
|
+
|
|
19
|
+
function make(name, logs, securityGroupId, subnetIds, intervalMinutesOpt, opts) {
|
|
20
|
+
let intervalMinutes = intervalMinutesOpt !== undefined ? intervalMinutesOpt : 1;
|
|
21
|
+
let customOpts = Stdlib_Option.map(opts, Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions);
|
|
22
|
+
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs", {}, undefined);
|
|
23
|
+
let logJsonOutputs = logs.map(l => Pulumi.all([
|
|
24
|
+
l.connectionConfig,
|
|
25
|
+
l.targetQueueUrl
|
|
26
|
+
]).apply(param => {
|
|
27
|
+
let cc = param[0];
|
|
28
|
+
let pgConnectionJson = Object.fromEntries([
|
|
29
|
+
[
|
|
30
|
+
"host",
|
|
31
|
+
cc.host
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
"port",
|
|
35
|
+
cc.port
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
"database",
|
|
39
|
+
cc.database
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
"username",
|
|
43
|
+
cc.username
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
"secretArn",
|
|
47
|
+
cc.secretArn
|
|
48
|
+
]
|
|
49
|
+
]);
|
|
50
|
+
return JSON.stringify(Object.fromEntries([
|
|
51
|
+
[
|
|
52
|
+
"pgConnection",
|
|
53
|
+
pgConnectionJson
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
"logName",
|
|
57
|
+
l.logName
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
"subscriber",
|
|
61
|
+
l.subscriber
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
"targetQueueUrl",
|
|
65
|
+
param[1]
|
|
66
|
+
]
|
|
67
|
+
]));
|
|
68
|
+
}));
|
|
69
|
+
let handlerConfigOutput = Pulumi.all(logJsonOutputs).apply(items => `{"logs":[` + items.join(",") + `]}`);
|
|
70
|
+
let envVars = Object.fromEntries([[
|
|
71
|
+
"HANDLER_CONFIG",
|
|
72
|
+
handlerConfigOutput
|
|
73
|
+
]]);
|
|
74
|
+
let vpcConfig = securityGroupId.apply(sgId => ({
|
|
75
|
+
subnetIds: subnetIds,
|
|
76
|
+
securityGroupIds: [sgId]
|
|
77
|
+
}));
|
|
78
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, undefined, undefined, undefined, undefined, undefined, undefined, vpcConfig, opts);
|
|
79
|
+
let secretArns = logs.map(l => l.connectionConfig.apply(cc => cc.secretArn));
|
|
80
|
+
let queueArns = logs.map(l => l.targetQueueArn);
|
|
81
|
+
Pulumi.all([
|
|
82
|
+
Pulumi.all(secretArns),
|
|
83
|
+
Pulumi.all(queueArns),
|
|
84
|
+
runtime.parts.lambdaRole.id
|
|
85
|
+
]).apply(param => {
|
|
86
|
+
new (Aws.iam.RolePolicy)(name + `RelayAccess`, {
|
|
87
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `RelayAccessPolicy`, [
|
|
88
|
+
{
|
|
89
|
+
Sid: "AllowGetSecret",
|
|
90
|
+
Effect: "Allow",
|
|
91
|
+
Action: "secretsmanager:GetSecretValue",
|
|
92
|
+
Resource: param[0]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
Sid: "AllowSendSqs",
|
|
96
|
+
Effect: "Allow",
|
|
97
|
+
Action: "sqs:SendMessage",
|
|
98
|
+
Resource: param[1]
|
|
99
|
+
}
|
|
100
|
+
])),
|
|
101
|
+
role: param[2]
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
let rule = new (Aws.cloudwatch.EventRule)(Pulumi.getStack() + `-` + name, {
|
|
105
|
+
description: "Poll Postgres DCB change feed and relay to EventCollector",
|
|
106
|
+
scheduleExpression: Primitive_option.some(Cloudwatch_EventRule$PulumiAws.ScheduleExpression.every({
|
|
107
|
+
TAG: "Minutes",
|
|
108
|
+
_0: intervalMinutes
|
|
109
|
+
}))
|
|
110
|
+
}, customOpts !== undefined ? Primitive_option.valFromOption(customOpts) : undefined);
|
|
111
|
+
let lambda = runtime.parts.lambda;
|
|
112
|
+
Pulumi.all([
|
|
113
|
+
Output$Pulumi.flatMap(lambda, l => l.arn),
|
|
114
|
+
Output$Pulumi.flatMap(lambda, l => l.name)
|
|
115
|
+
]).apply(param => {
|
|
116
|
+
new (Aws.lambda.Permission)(name + `SchedulePermission`, {
|
|
117
|
+
action: "lambda:InvokeFunction",
|
|
118
|
+
function: param[1],
|
|
119
|
+
principal: AWS$ReventlessAws.CloudwatchEventRule.principal
|
|
120
|
+
}, customOpts !== undefined ? Primitive_option.valFromOption(customOpts) : undefined);
|
|
121
|
+
new (Aws.cloudwatch.EventTarget)(name, {
|
|
122
|
+
rule: Cloudwatch_EventTarget$PulumiAws.Rule.ofEventRule(rule),
|
|
123
|
+
arn: param[0]
|
|
124
|
+
}, customOpts !== undefined ? Primitive_option.valFromOption(customOpts) : undefined);
|
|
125
|
+
});
|
|
126
|
+
return [
|
|
127
|
+
Adapter$ReventlessCore.outputToResource(lambda.apply(l => Util_Lambda$ReventlessAws.toResource(undefined, l))),
|
|
128
|
+
Util_Cloudwatch$ReventlessAws.EventRule.toResource(rule)
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
make,
|
|
134
|
+
}
|
|
135
|
+
/* @pulumi/aws Not a pure module */
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Postgres change-feed relay runtime (B2.2).
|
|
2
|
+
//
|
|
3
|
+
// Drains a Postgres DCB log via `PgChangeFeed` and pushes each event — transformed
|
|
4
|
+
// into the `{id, meta, event}` shape the plugin EventCollector SQS handler
|
|
5
|
+
// consumes — onto that queue. The plugin EventCollector (`AdminEventCollectorEntryPoint`)
|
|
6
|
+
// then drives the full fan-out: read-model projections, aggregate command topics,
|
|
7
|
+
// and the cross-plugin SNS EventTopic. See
|
|
8
|
+
// docs/plans/aws-postgres-change-feed-bridge.md.
|
|
9
|
+
//
|
|
10
|
+
// The transform reuses the canonical DynamoDB shape producers
|
|
11
|
+
// (`derivePartitionKey` + `buildJsonEvent'`) so the emitted body is byte-identical
|
|
12
|
+
// to the DynamoDB-stream path — the SQS handler cannot tell the difference.
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Transform one feed event into the EventCollector JSON body. Rebuilds the same
|
|
16
|
+
* unmarshalled-item dict the DCB DynamoDB-stream decoder would see (id from the
|
|
17
|
+
* partition tag, position, event type, data, decomposed meta) and runs it through
|
|
18
|
+
* `buildJsonEvent'` — the exact decoder the SQS handler expects. Returns `None`
|
|
19
|
+
* for a malformed event (mirrors `buildJsonEvent'`).
|
|
20
|
+
*/
|
|
21
|
+
let toEventCollectorJson = (
|
|
22
|
+
event: ReventlessCore.DcbEventLog_Adapter.rawSequencedEvent,
|
|
23
|
+
~partitionTag: option<Reventless.DcbTag.derivedPartitionTag>=?,
|
|
24
|
+
): option<JSON.t> => {
|
|
25
|
+
let item = Dict.make()
|
|
26
|
+
item->Dict.set(
|
|
27
|
+
"id",
|
|
28
|
+
DcbEventLogStorage_DynamoDb_Runtime.derivePartitionKey(~partitionTag?, event.tags)
|
|
29
|
+
->JSON.Encode.string,
|
|
30
|
+
)
|
|
31
|
+
item->Dict.set("position", event.position->JSON.Encode.string)
|
|
32
|
+
item->Dict.set("event", event.eventType->JSON.Encode.string)
|
|
33
|
+
item->Dict.set("data", event.data)
|
|
34
|
+
ReventlessCore.Message.decomposeMeta(event.meta)->Array.forEach(((key, value)) =>
|
|
35
|
+
item->Dict.set(key, value)
|
|
36
|
+
)
|
|
37
|
+
Util_DynamoDbStream_Runtime.buildJsonEvent'(item)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Drain a Postgres DCB log from its checkpoint and relay its events to the
|
|
42
|
+
* EventCollector via the injected `sendBatch`. `sendBatch` is injected (rather than
|
|
43
|
+
* hardcoding SQS) so the drain/transform is unit-testable; the relay entry point
|
|
44
|
+
* wires it to the actual `SendMessage`. Returns the number of events processed.
|
|
45
|
+
*
|
|
46
|
+
* At-least-once by construction: `PgChangeFeed.drain` replays the last page on a
|
|
47
|
+
* crash before checkpoint, and EventCollector projections are idempotent
|
|
48
|
+
* (event-sourced), so re-delivery is safe.
|
|
49
|
+
*/
|
|
50
|
+
let relay = async (
|
|
51
|
+
~config: PgConnection.connectionConfig,
|
|
52
|
+
~logName: string,
|
|
53
|
+
~subscriber: string,
|
|
54
|
+
~partitionTag: option<Reventless.DcbTag.derivedPartitionTag>=?,
|
|
55
|
+
~sendBatch: array<JSON.t> => promise<unit>,
|
|
56
|
+
): int => {
|
|
57
|
+
let pool = PgRuntime.poolFor(config)
|
|
58
|
+
await ReventlessPostgres.PgChangeFeed.drain(pool, ~logName, ~subscriber, ~handle=async events => {
|
|
59
|
+
let jsons = events->Array.filterMap(event => toEventCollectorJson(event, ~partitionTag?))
|
|
60
|
+
if jsons->Array.length > 0 {
|
|
61
|
+
await sendBatch(jsons)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
|
+
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
5
|
+
import * as PgRuntime$ReventlessAws from "./PgRuntime.res.mjs";
|
|
6
|
+
import * as PgChangeFeed$ReventlessPostgres from "@reventlessdev/reventless-postgres/src/PgChangeFeed.res.mjs";
|
|
7
|
+
import * as Util_DynamoDbStream_Runtime$ReventlessAws from "../../util/Util_DynamoDbStream_Runtime.res.mjs";
|
|
8
|
+
import * as DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws from "../DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs";
|
|
9
|
+
|
|
10
|
+
function toEventCollectorJson(event, partitionTag) {
|
|
11
|
+
let item = {};
|
|
12
|
+
item["id"] = DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws.derivePartitionKey(partitionTag, event.tags);
|
|
13
|
+
item["position"] = event.position;
|
|
14
|
+
item["event"] = event.eventType;
|
|
15
|
+
item["data"] = event.data;
|
|
16
|
+
Message$ReventlessCore.decomposeMeta(event.meta).forEach(param => {
|
|
17
|
+
item[param[0]] = param[1];
|
|
18
|
+
});
|
|
19
|
+
return Util_DynamoDbStream_Runtime$ReventlessAws.buildJsonEvent$p(item);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function relay(config, logName, subscriber, partitionTag, sendBatch) {
|
|
23
|
+
let pool = PgRuntime$ReventlessAws.poolFor(config);
|
|
24
|
+
return await PgChangeFeed$ReventlessPostgres.drain(pool, logName, subscriber, undefined, async events => {
|
|
25
|
+
let jsons = Stdlib_Array.filterMap(events, event => toEventCollectorJson(event, partitionTag));
|
|
26
|
+
if (jsons.length !== 0) {
|
|
27
|
+
return await sendBatch(jsons);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
toEventCollectorJson,
|
|
34
|
+
relay,
|
|
35
|
+
}
|
|
36
|
+
/* Message-ReventlessCore Not a pure module */
|
|
@@ -12,6 +12,7 @@ import { Make as commandTopicCallbackMake } from "@reventlessdev/reventless-core
|
|
|
12
12
|
import { makeGenerateCommand } from "@reventlessdev/reventless-core/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs";
|
|
13
13
|
import { commandOutcomeToJson, runInlineAndCollect } from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Helpers.res.mjs";
|
|
14
14
|
import { append, replay, replayStream, appendStream } from "@reventlessdev/reventless-aws/src/adapter/EventLog/EventLogStorage_DynamoDb_Runtime.res.mjs";
|
|
15
|
+
import { opsFor as pgEventLogOpsFor } from "@reventlessdev/reventless-aws/src/adapter/EventLog/EventLogStorage_Postgres_Runtime.res.mjs";
|
|
15
16
|
import { handleQueueEvent, publishJsons as sqsPublishJsons } from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
16
17
|
import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";
|
|
17
18
|
import { unmarshall } from "@aws-sdk/util-dynamodb";
|
|
@@ -118,15 +119,32 @@ async function checkPluginStatus(event) {
|
|
|
118
119
|
// Route 2 (SQS event source): the in-process command handler (used both as the
|
|
119
120
|
// inline dispatch target and as the SQS message handler) plus the resolved
|
|
120
121
|
// queue ref for the fire-and-forget async path.
|
|
121
|
-
|
|
122
|
+
// `pgConnection`, when present, is the resolved PgConnection.connectionConfig
|
|
123
|
+
// ({host,port,database,username,secretArn}) serialized into HANDLER_CONFIG by the
|
|
124
|
+
// runtime builder for a Postgres-backed aggregate. Its presence selects the
|
|
125
|
+
// Postgres EventLog runtime; absence keeps the DynamoDB path byte-identical.
|
|
126
|
+
// `eventLogTableName` doubles as the Postgres `event_log.log_name` discriminator
|
|
127
|
+
// (all aggregates share one Postgres table, unlike DynamoDB's table-per-aggregate).
|
|
128
|
+
function buildAggregateParts(specModule, behaviorModule, eventLogTableName, queueUrl, pgConnection) {
|
|
122
129
|
const patchedSpec = patchSpecId(specModule);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
let rawStorageOps;
|
|
131
|
+
if (pgConnection) {
|
|
132
|
+
const pgOps = pgEventLogOpsFor(pgConnection, eventLogTableName);
|
|
133
|
+
rawStorageOps = {
|
|
134
|
+
append: pgOps.append,
|
|
135
|
+
replay: pgOps.replay,
|
|
136
|
+
replayStream: pgOps.replayStream,
|
|
137
|
+
appendStream: pgOps.appendStream,
|
|
138
|
+
};
|
|
139
|
+
} else {
|
|
140
|
+
const resolvedTable = { name: eventLogTableName };
|
|
141
|
+
rawStorageOps = {
|
|
142
|
+
append: append(resolvedTable),
|
|
143
|
+
replay: replay(resolvedTable),
|
|
144
|
+
replayStream: replayStream(resolvedTable),
|
|
145
|
+
appendStream: appendStream(resolvedTable),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
130
148
|
const eventLogOps = eventLogOperationsMake(patchedSpec)({
|
|
131
149
|
Spec: patchedSpec,
|
|
132
150
|
EventTopic: { Spec: patchedSpec },
|
|
@@ -246,7 +264,7 @@ async function buildAllHandlers() {
|
|
|
246
264
|
await Promise.all(config.handlers.map(async h => {
|
|
247
265
|
const specModule = await dynamicImport(h.specModule);
|
|
248
266
|
const behaviorModule = await dynamicImport(h.behaviorModule);
|
|
249
|
-
const parts = buildAggregateParts(specModule, behaviorModule, h.eventLogTable, h.queueUrl);
|
|
267
|
+
const parts = buildAggregateParts(specModule, behaviorModule, h.eventLogTable, h.queueUrl, h.pgConnection);
|
|
250
268
|
cmdTopicHandlers[h.queueArn] = parts.sqsHandler;
|
|
251
269
|
cmdGenHandlers[specModule.name] = buildCommandGeneratorHandler(parts, DISPATCH_MODE);
|
|
252
270
|
}));
|