@reventlessdev/reventless-aws 3.0.0-alpha.176 → 3.0.0-alpha.178
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 +23 -0
- package/docker-compose.postgres.yml +21 -0
- package/package.json +7 -6
- package/scripts/run-pg-integration-tests.sh +58 -0
- package/src/Platform.res +49 -0
- package/src/Platform.res.mjs +48 -2
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +7 -1
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +10 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +7 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +10 -1
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +6 -21
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +2 -17
- package/src/adapter/DcbEventLog/DcbBackend.res +4 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res +2 -2
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res.mjs +7 -1
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -11
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +2 -24
- package/src/adapter/Postgres/PgConnection.res +70 -10
- package/src/adapter/Postgres/PgConnection.res.mjs +48 -3
- package/src/adapter/Postgres/PgMigration_Builder.res +127 -0
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +51 -0
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res +279 -0
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +170 -0
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +202 -11
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +150 -9
- package/src/adapter/QueryDb/QueryDbBackend.res +8 -0
- package/src/adapter/QueryDb/QueryDbBackend.res.mjs +4 -1
- package/src/adapter/QueryDb/QueryDbResolvers.res +6 -5
- package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +6 -3
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res +288 -0
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res.mjs +170 -0
- package/src/adapter/QueryDb/QueryDbStorage.res +6 -1
- package/src/adapter/QueryDb/QueryDbStorage.res.mjs +2 -1
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +5 -1
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +3 -3
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +3 -14
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +2 -25
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +3 -14
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +2 -25
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +7 -1
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +78 -15
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +43 -26
- package/src/adapter/Runtime/PgMigrationEntryPoint.mjs +40 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +88 -0
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +17 -5
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +10 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +3 -0
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +9 -14
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +2 -25
- package/src/adapter/Runtime/StateTopicPublish.mjs +204 -0
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +17 -3
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +86 -17
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +50 -33
- package/src/adapter/StateTopic/StateTopic_AppSync.res +7 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +10 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res +7 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +10 -1
- package/src/util/Util_Bundle.res +71 -1
- package/src/util/Util_Bundle.res.mjs +49 -1
- package/tests/PgChangeFeedRelay_IntegrationTest.res +3 -3
- package/tests/PgConnectionJsonTest.res +84 -0
- package/tests/PgConnectionJsonTest.res.mjs +68 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res +82 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +63 -0
- package/tests/PgPipeline_IntegrationTest.res +3 -1
- package/tests/PgQueryResolver_LambdaTest.res +163 -2
- package/tests/PgQueryResolver_LambdaTest.res.mjs +226 -14
- package/tests/StateTopicPublishTest.res +162 -0
- package/tests/StateTopicPublishTest.res.mjs +158 -0
- package/tests/Util_BundleEsmLoaderTest.res +57 -0
- package/tests/Util_BundleEsmLoaderTest.res.mjs +44 -0
|
@@ -201,20 +201,9 @@ let finish = () =>
|
|
|
201
201
|
let pgSelection = EventLogBackend.get()
|
|
202
202
|
let pgConnectionFragment = switch pgSelection {
|
|
203
203
|
| Some(sel) =>
|
|
204
|
-
sel.connectionConfig->Pulumi.Output.apply(cc =>
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
("host", cc.host->JSON.Encode.string),
|
|
208
|
-
("port", cc.port->Int.toFloat->JSON.Encode.float),
|
|
209
|
-
("database", cc.database->JSON.Encode.string),
|
|
210
|
-
("username", cc.username->JSON.Encode.string),
|
|
211
|
-
("secretArn", cc.secretArn->JSON.Encode.string),
|
|
212
|
-
]
|
|
213
|
-
->Dict.fromArray
|
|
214
|
-
->JSON.Encode.object
|
|
215
|
-
->JSON.stringify
|
|
216
|
-
`,"pgConnection":${pgConnectionJson}`
|
|
217
|
-
})
|
|
204
|
+
sel.connectionConfig->Pulumi.Output.apply(cc =>
|
|
205
|
+
`,"pgConnection":${cc->PgConnection.connectionConfigToJson->JSON.stringify}`
|
|
206
|
+
)
|
|
218
207
|
| None => Pulumi.Output.make("")
|
|
219
208
|
}
|
|
220
209
|
|
|
@@ -10,6 +10,7 @@ import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/
|
|
|
10
10
|
import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
|
|
11
11
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
12
12
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
13
|
+
import * as PgConnection$ReventlessAws from "../Postgres/PgConnection.res.mjs";
|
|
13
14
|
import * as EventLogBackend$ReventlessAws from "../EventLog/EventLogBackend.res.mjs";
|
|
14
15
|
import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
|
|
15
16
|
import * as CommandTopicChannel_SQS_Async$ReventlessAws from "../CommandTopic/CommandTopicChannel_SQS_Async.res.mjs";
|
|
@@ -148,31 +149,7 @@ function finish() {
|
|
|
148
149
|
let handlerOutputs = [];
|
|
149
150
|
let packageDirs = {};
|
|
150
151
|
let pgSelection = EventLogBackend$ReventlessAws.get();
|
|
151
|
-
let pgConnectionFragment = pgSelection !== undefined ? pgSelection.connectionConfig.apply(cc =>
|
|
152
|
-
let pgConnectionJson = JSON.stringify(Object.fromEntries([
|
|
153
|
-
[
|
|
154
|
-
"host",
|
|
155
|
-
cc.host
|
|
156
|
-
],
|
|
157
|
-
[
|
|
158
|
-
"port",
|
|
159
|
-
cc.port
|
|
160
|
-
],
|
|
161
|
-
[
|
|
162
|
-
"database",
|
|
163
|
-
cc.database
|
|
164
|
-
],
|
|
165
|
-
[
|
|
166
|
-
"username",
|
|
167
|
-
cc.username
|
|
168
|
-
],
|
|
169
|
-
[
|
|
170
|
-
"secretArn",
|
|
171
|
-
cc.secretArn
|
|
172
|
-
]
|
|
173
|
-
]));
|
|
174
|
-
return `,"pgConnection":` + pgConnectionJson;
|
|
175
|
-
}) : Pulumi.output("");
|
|
152
|
+
let pgConnectionFragment = pgSelection !== undefined ? pgSelection.connectionConfig.apply(cc => `,"pgConnection":` + JSON.stringify(PgConnection$ReventlessAws.connectionConfigToJson(undefined, cc))) : Pulumi.output("");
|
|
176
153
|
specs.forEach(spec => {
|
|
177
154
|
let info = aggregateInfos[spec.aggregateName];
|
|
178
155
|
if (info === undefined) {
|
|
@@ -99,7 +99,13 @@ export async function buildHandlersForConfig(config, opts = {}) {
|
|
|
99
99
|
// (see docs/plans/aws-postgres-change-feed-bridge.md), not by this Lambda.
|
|
100
100
|
const rawStorageOps = config.pgConnection
|
|
101
101
|
? (() => {
|
|
102
|
-
|
|
102
|
+
// C2: `lockStrategy` ("AdvisoryLocks" | "RowLocks") is the polyvariant's
|
|
103
|
+
// string name; `opsFor` defaults to "AdvisoryLocks" when it's absent.
|
|
104
|
+
const pgOps = pgDcbEventLogOpsFor(
|
|
105
|
+
config.pgConnection,
|
|
106
|
+
config.dcbEventLogTableName,
|
|
107
|
+
config.pgConnection.lockStrategy,
|
|
108
|
+
);
|
|
103
109
|
return { read: pgOps.read, append: pgOps.append, readStream: pgOps.readStream };
|
|
104
110
|
})()
|
|
105
111
|
: (() => {
|
|
@@ -18,6 +18,18 @@ type readModelInfo = {
|
|
|
18
18
|
|
|
19
19
|
let readModelInfos: dict<readModelInfo> = Dict.make()
|
|
20
20
|
|
|
21
|
+
// B3.3: the AppSync Events API a Postgres-backed stream read model publishes live
|
|
22
|
+
// updates to. Set once by `Platform.makePlatform` (monolithic mode) before the
|
|
23
|
+
// plugins build, so `finish` can add the APPSYNC_ENDPOINT env + appsync:EventPublish
|
|
24
|
+
// IAM to the projection Lambda. None → no live updates (no events API, or the
|
|
25
|
+
// platform runs without one).
|
|
26
|
+
type eventsApiConfig = {
|
|
27
|
+
endpoint: Pulumi.Output.t<string>,
|
|
28
|
+
apiArn: Pulumi.Output.t<string>,
|
|
29
|
+
}
|
|
30
|
+
let eventsApiConfig: ref<option<eventsApiConfig>> = ref(None)
|
|
31
|
+
let setEventsApiConfig = (cfg: eventsApiConfig) => eventsApiConfig := Some(cfg)
|
|
32
|
+
|
|
21
33
|
let registerReadModel = (
|
|
22
34
|
~readModelName,
|
|
23
35
|
~specModulePath,
|
|
@@ -147,20 +159,9 @@ let finish = () =>
|
|
|
147
159
|
readModelInfos->Dict.valuesToArray->Array.some(info => info.pgBacked)
|
|
148
160
|
let pgConnectionFragment = switch (qdbSelection, anyPgBacked) {
|
|
149
161
|
| (Some(sel), true) =>
|
|
150
|
-
sel.connectionConfig->Pulumi.Output.apply(cc =>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
("host", cc.host->JSON.Encode.string),
|
|
154
|
-
("port", cc.port->Int.toFloat->JSON.Encode.float),
|
|
155
|
-
("database", cc.database->JSON.Encode.string),
|
|
156
|
-
("username", cc.username->JSON.Encode.string),
|
|
157
|
-
("secretArn", cc.secretArn->JSON.Encode.string),
|
|
158
|
-
]
|
|
159
|
-
->Dict.fromArray
|
|
160
|
-
->JSON.Encode.object
|
|
161
|
-
->JSON.stringify
|
|
162
|
-
`,"pgConnection":${pgConnectionJson}`
|
|
163
|
-
})
|
|
162
|
+
sel.connectionConfig->Pulumi.Output.apply(cc =>
|
|
163
|
+
`,"pgConnection":${cc->PgConnection.connectionConfigToJson->JSON.stringify}`
|
|
164
|
+
)
|
|
164
165
|
| _ => Pulumi.Output.make("")
|
|
165
166
|
}
|
|
166
167
|
|
|
@@ -192,6 +193,23 @@ let finish = () =>
|
|
|
192
193
|
let handlerPgFragment = info.pgBacked
|
|
193
194
|
? pgConnectionFragment
|
|
194
195
|
: Pulumi.Output.make("")
|
|
196
|
+
// B3.3: a subscription-enabled Postgres read model publishes live
|
|
197
|
+
// updates from the projection Lambda — bake its AppSync Events channel
|
|
198
|
+
// root (the plural LIST field name, same source as the DynamoDB
|
|
199
|
+
// subscriptionInfraHook). Deploy-time resolved, so a plain string.
|
|
200
|
+
let stateTopicFragment =
|
|
201
|
+
info.pgBacked &&
|
|
202
|
+
QueryDbBackend.postgresStreamRegistry->Set.has(spec.componentName) &&
|
|
203
|
+
eventsApiConfig.contents->Option.isSome
|
|
204
|
+
? {
|
|
205
|
+
let topic =
|
|
206
|
+
ReventlessCore.Plugin_Helpers.queryFieldNamesRegistry
|
|
207
|
+
->Dict.get(spec.componentName)
|
|
208
|
+
->Option.map(qn => qn.listFieldName)
|
|
209
|
+
->Option.getOr(spec.componentName)
|
|
210
|
+
`,"stateTopicName":${topic->JSON.Encode.string->JSON.stringify}`
|
|
211
|
+
}
|
|
212
|
+
: ""
|
|
195
213
|
let handlerJson =
|
|
196
214
|
Pulumi.Output.all4((
|
|
197
215
|
info.queryDbTableName,
|
|
@@ -203,7 +221,7 @@ let finish = () =>
|
|
|
203
221
|
// No stream resource (Postgres-backed source) → dispatch on the
|
|
204
222
|
// feed queue's ARN instead of a stream URN.
|
|
205
223
|
let sourceUrn = urns->Array.get(0)->Option.getOr(feedArn)
|
|
206
|
-
`{"specModule":${specModule},"mappingsModule":${mappingsModule},"queryDbTableName":"${tableName}","sourceUrn":"${sourceUrn}"${pgFragment}}`
|
|
224
|
+
`{"specModule":${specModule},"mappingsModule":${mappingsModule},"queryDbTableName":"${tableName}","sourceUrn":"${sourceUrn}"${pgFragment}${stateTopicFragment}}`
|
|
207
225
|
})
|
|
208
226
|
let _ = handlerOutputs->Array.push(handlerJson)
|
|
209
227
|
| None =>
|
|
@@ -223,6 +241,22 @@ let finish = () =>
|
|
|
223
241
|
let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
|
|
224
242
|
envVars->Dict.set("HANDLER_CONFIG", handlerConfigOutput->Pulumi.Output.asInput)
|
|
225
243
|
|
|
244
|
+
// B3.3: when any Postgres-backed read model is subscription-enabled, the
|
|
245
|
+
// projection Lambda publishes live updates to the AppSync Events API —
|
|
246
|
+
// give it the endpoint (env) and appsync:EventPublish (IAM, below).
|
|
247
|
+
let anyPgStream =
|
|
248
|
+
readModelInfos
|
|
249
|
+
->Dict.keysToArray
|
|
250
|
+
->Array.some(rmName =>
|
|
251
|
+
(readModelInfos->Dict.get(rmName)->Option.mapOr(false, i => i.pgBacked)) &&
|
|
252
|
+
QueryDbBackend.postgresStreamRegistry->Set.has(rmName)
|
|
253
|
+
)
|
|
254
|
+
let pgStreamConfig = anyPgStream ? eventsApiConfig.contents : None
|
|
255
|
+
switch pgStreamConfig {
|
|
256
|
+
| Some(cfg) => envVars->Dict.set("APPSYNC_ENDPOINT", cfg.endpoint->Pulumi.Output.asInput)
|
|
257
|
+
| None => ()
|
|
258
|
+
}
|
|
259
|
+
|
|
226
260
|
let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
227
261
|
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/ReadModelEntryPoint.mjs",
|
|
228
262
|
~packageDirs,
|
|
@@ -287,6 +321,35 @@ let finish = () =>
|
|
|
287
321
|
| _ => ()
|
|
288
322
|
}
|
|
289
323
|
|
|
324
|
+
// B3.3: grant the projection Lambda appsync:EventPublish on the events API
|
|
325
|
+
// so a Postgres-backed stream read model can push live-update descriptors.
|
|
326
|
+
switch pgStreamConfig {
|
|
327
|
+
| Some(cfg) =>
|
|
328
|
+
let _ = cfg.apiArn->Pulumi.Output.apply(apiArn => {
|
|
329
|
+
open PulumiAws.PolicyDocument
|
|
330
|
+
PulumiAws.IAM.RolePolicy.make(
|
|
331
|
+
~name="AllReadModels-appsyncPublish",
|
|
332
|
+
~args={
|
|
333
|
+
policy: PulumiAws.PolicyDocument.make(
|
|
334
|
+
~id="AllReadModels-appsyncPublishPolicy",
|
|
335
|
+
~statements=[
|
|
336
|
+
{
|
|
337
|
+
sid: "AllowPublishAppSyncEvents",
|
|
338
|
+
effect: Allow,
|
|
339
|
+
actions: Action("appsync:EventPublish"),
|
|
340
|
+
resources: Resource(apiArn ++ "/*"),
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
)
|
|
344
|
+
->PulumiAws.PolicyDocument.toJsonString
|
|
345
|
+
->Pulumi.Input.make,
|
|
346
|
+
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
347
|
+
},
|
|
348
|
+
)
|
|
349
|
+
})
|
|
350
|
+
| None => ()
|
|
351
|
+
}
|
|
352
|
+
|
|
290
353
|
let channelSpecs = storedSpecs->Array.map(({channelSpec}) => channelSpec)
|
|
291
354
|
let _connectResources = EventCollectorChannel.connect(
|
|
292
355
|
~name="AllReadModels",
|
|
@@ -11,9 +11,11 @@ import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/co
|
|
|
11
11
|
import * as DcbBackend$ReventlessAws from "../DcbEventLog/DcbBackend.res.mjs";
|
|
12
12
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
13
13
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
14
|
+
import * as PgConnection$ReventlessAws from "../Postgres/PgConnection.res.mjs";
|
|
14
15
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
15
16
|
import * as QueryDbBackend$ReventlessAws from "../QueryDb/QueryDbBackend.res.mjs";
|
|
16
17
|
import * as EventLogBackend$ReventlessAws from "../EventLog/EventLogBackend.res.mjs";
|
|
18
|
+
import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
|
|
17
19
|
import * as PgProjectionFeed$ReventlessAws from "../Postgres/PgProjectionFeed.res.mjs";
|
|
18
20
|
import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
|
|
19
21
|
import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
|
|
@@ -22,6 +24,14 @@ let log = Logger$ReventlessCore.fromEnv();
|
|
|
22
24
|
|
|
23
25
|
let readModelInfos = {};
|
|
24
26
|
|
|
27
|
+
let eventsApiConfig = {
|
|
28
|
+
contents: undefined
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function setEventsApiConfig(cfg) {
|
|
32
|
+
eventsApiConfig.contents = cfg;
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
function registerReadModel(readModelName, specModulePath, mappingsModulePath, queryDbTableName, pgBackedOpt) {
|
|
26
36
|
let pgBacked = pgBackedOpt !== undefined ? pgBackedOpt : false;
|
|
27
37
|
readModelInfos[readModelName] = {
|
|
@@ -95,31 +105,7 @@ function finish() {
|
|
|
95
105
|
let feedArnOutput = feedQueue !== undefined ? feedQueue.arn : Pulumi.output("");
|
|
96
106
|
let qdbSelection = QueryDbBackend$ReventlessAws.get();
|
|
97
107
|
let anyPgBacked = Object.values(readModelInfos).some(info => info.pgBacked);
|
|
98
|
-
let pgConnectionFragment = qdbSelection !== undefined && anyPgBacked ? qdbSelection.connectionConfig.apply(cc =>
|
|
99
|
-
let pgConnectionJson = JSON.stringify(Object.fromEntries([
|
|
100
|
-
[
|
|
101
|
-
"host",
|
|
102
|
-
cc.host
|
|
103
|
-
],
|
|
104
|
-
[
|
|
105
|
-
"port",
|
|
106
|
-
cc.port
|
|
107
|
-
],
|
|
108
|
-
[
|
|
109
|
-
"database",
|
|
110
|
-
cc.database
|
|
111
|
-
],
|
|
112
|
-
[
|
|
113
|
-
"username",
|
|
114
|
-
cc.username
|
|
115
|
-
],
|
|
116
|
-
[
|
|
117
|
-
"secretArn",
|
|
118
|
-
cc.secretArn
|
|
119
|
-
]
|
|
120
|
-
]));
|
|
121
|
-
return `,"pgConnection":` + pgConnectionJson;
|
|
122
|
-
}) : Pulumi.output("");
|
|
108
|
+
let pgConnectionFragment = qdbSelection !== undefined && anyPgBacked ? qdbSelection.connectionConfig.apply(cc => `,"pgConnection":` + JSON.stringify(PgConnection$ReventlessAws.connectionConfigToJson(undefined, cc))) : Pulumi.output("");
|
|
123
109
|
let handlerOutputs = [];
|
|
124
110
|
let packageDirs = {};
|
|
125
111
|
let sortedSpecs = storedSpecs.toSorted((a, b) => Primitive_string.compare(a.componentName, b.componentName));
|
|
@@ -135,6 +121,13 @@ function finish() {
|
|
|
135
121
|
let specModule = Stdlib_Option.getOr(JSON.stringify(info.specModulePath), `""`);
|
|
136
122
|
let mappingsModule = Stdlib_Option.getOr(JSON.stringify(info.mappingsModulePath), `""`);
|
|
137
123
|
let handlerPgFragment = info.pgBacked ? pgConnectionFragment : Pulumi.output("");
|
|
124
|
+
let stateTopicFragment;
|
|
125
|
+
if (info.pgBacked && QueryDbBackend$ReventlessAws.postgresStreamRegistry.has(spec.componentName) && Stdlib_Option.isSome(eventsApiConfig.contents)) {
|
|
126
|
+
let topic = Stdlib_Option.getOr(Stdlib_Option.map(Plugin_Helpers$ReventlessCore.queryFieldNamesRegistry[spec.componentName], qn => qn.listFieldName), spec.componentName);
|
|
127
|
+
stateTopicFragment = `,"stateTopicName":` + JSON.stringify(topic);
|
|
128
|
+
} else {
|
|
129
|
+
stateTopicFragment = "";
|
|
130
|
+
}
|
|
138
131
|
let handlerJson = Pulumi.all([
|
|
139
132
|
info.queryDbTableName,
|
|
140
133
|
spec.sourceUrns,
|
|
@@ -142,13 +135,24 @@ function finish() {
|
|
|
142
135
|
handlerPgFragment
|
|
143
136
|
]).apply(param => {
|
|
144
137
|
let sourceUrn = Stdlib_Option.getOr(param[1][0], param[2]);
|
|
145
|
-
return `{"specModule":` + specModule + `,"mappingsModule":` + mappingsModule + `,"queryDbTableName":"` + param[0] + `","sourceUrn":"` + sourceUrn + `"` + param[3] + `}`;
|
|
138
|
+
return `{"specModule":` + specModule + `,"mappingsModule":` + mappingsModule + `,"queryDbTableName":"` + param[0] + `","sourceUrn":"` + sourceUrn + `"` + param[3] + stateTopicFragment + `}`;
|
|
146
139
|
});
|
|
147
140
|
handlerOutputs.push(handlerJson);
|
|
148
141
|
});
|
|
149
142
|
let handlerConfigOutput = Pulumi.all(handlerOutputs).apply(handlers => `{"handlers":[` + handlers.join(",") + `]}`);
|
|
150
143
|
let envVars = {};
|
|
151
144
|
envVars["HANDLER_CONFIG"] = handlerConfigOutput;
|
|
145
|
+
let anyPgStream = Object.keys(readModelInfos).some(rmName => {
|
|
146
|
+
if (Stdlib_Option.mapOr(readModelInfos[rmName], false, i => i.pgBacked)) {
|
|
147
|
+
return QueryDbBackend$ReventlessAws.postgresStreamRegistry.has(rmName);
|
|
148
|
+
} else {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
let pgStreamConfig = anyPgStream ? eventsApiConfig.contents : undefined;
|
|
153
|
+
if (pgStreamConfig !== undefined) {
|
|
154
|
+
envVars["APPSYNC_ENDPOINT"] = pgStreamConfig.endpoint;
|
|
155
|
+
}
|
|
152
156
|
let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/ReadModelEntryPoint.mjs", packageDirs, undefined);
|
|
153
157
|
let vpcConfig = qdbSelection !== undefined && anyPgBacked ? qdbSelection.securityGroupId.apply(sgId => ({
|
|
154
158
|
subnetIds: qdbSelection.subnetIds,
|
|
@@ -166,6 +170,17 @@ function finish() {
|
|
|
166
170
|
role: runtime.parts.lambdaRole.id
|
|
167
171
|
}));
|
|
168
172
|
}
|
|
173
|
+
if (pgStreamConfig !== undefined) {
|
|
174
|
+
pgStreamConfig.apiArn.apply(apiArn => new (Aws.iam.RolePolicy)("AllReadModels-appsyncPublish", {
|
|
175
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, "AllReadModels-appsyncPublishPolicy", [{
|
|
176
|
+
Sid: "AllowPublishAppSyncEvents",
|
|
177
|
+
Effect: "Allow",
|
|
178
|
+
Action: "appsync:EventPublish",
|
|
179
|
+
Resource: apiArn + "/*"
|
|
180
|
+
}])),
|
|
181
|
+
role: runtime.parts.lambdaRole.id
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
169
184
|
let channelSpecs = storedSpecs.map(param => param.channelSpec);
|
|
170
185
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllReadModels", channelSpecs, runtime, opts);
|
|
171
186
|
if (feedQueue !== undefined) {
|
|
@@ -187,6 +202,8 @@ export {
|
|
|
187
202
|
RuntimeEnvironment,
|
|
188
203
|
log,
|
|
189
204
|
readModelInfos,
|
|
205
|
+
eventsApiConfig,
|
|
206
|
+
setEventsApiConfig,
|
|
190
207
|
registerReadModel,
|
|
191
208
|
storedSpecs,
|
|
192
209
|
grandParent,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// One-shot Postgres schema-migration Lambda entry point (A3).
|
|
2
|
+
//
|
|
3
|
+
// Invoked once during `pulumi up` by an `aws.lambda.Invocation` resource, from
|
|
4
|
+
// INSIDE the DB's VPC, so the deploy runner needs no network path to a private
|
|
5
|
+
// RDS instance. Runs the exact idempotent `PgSchema.ensureSchema` the local
|
|
6
|
+
// backend runs at startup (reventless-local's `postgres` smart constructor), so
|
|
7
|
+
// the AWS schema is byte-identical to local — zero drift. Every statement is
|
|
8
|
+
// `IF NOT EXISTS`, so re-invocation (e.g. after a schema-DDL change bumps the
|
|
9
|
+
// Lambda layer) is safe.
|
|
10
|
+
//
|
|
11
|
+
// HANDLER_CONFIG shape:
|
|
12
|
+
// { "pgConnection": { host, port, database, username, secretArn } }
|
|
13
|
+
|
|
14
|
+
import { poolFor } from "@reventlessdev/reventless-aws/src/adapter/Postgres/PgRuntime.res.mjs";
|
|
15
|
+
import { ensureSchema } from "@reventlessdev/reventless-postgres/src/PgSchema.res.mjs";
|
|
16
|
+
import { log } from "./HandlerFactoryHelpers.mjs";
|
|
17
|
+
|
|
18
|
+
// Config parse → pool → ensureSchema, with the pool source injectable so the
|
|
19
|
+
// orchestration is integration-testable against a real Postgres without the
|
|
20
|
+
// Secrets Manager round-trip (mirrors PgChangeFeedRelay_Runtime's
|
|
21
|
+
// relayWithPool/relay split). `opts.makePool` defaults to the production `poolFor`.
|
|
22
|
+
export async function runMigration(config, opts = {}) {
|
|
23
|
+
if (!config.pgConnection) {
|
|
24
|
+
throw new Error("PgMigrationEntryPoint: HANDLER_CONFIG has no pgConnection");
|
|
25
|
+
}
|
|
26
|
+
// `poolFor` memoises one pool per secret ARN per container and resolves the
|
|
27
|
+
// RDS-managed password from Secrets Manager via a cached provider.
|
|
28
|
+
const makePool = opts.makePool ?? poolFor;
|
|
29
|
+
const pool = makePool(config.pgConnection);
|
|
30
|
+
await ensureSchema(pool);
|
|
31
|
+
log.info("ensureSchema completed for " + config.pgConnection.database, {
|
|
32
|
+
comp: "PgMigration",
|
|
33
|
+
});
|
|
34
|
+
return "ok";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function handler(_event, _context) {
|
|
38
|
+
const config = JSON.parse(process.env["HANDLER_CONFIG"] || "{}");
|
|
39
|
+
return runMigration(config);
|
|
40
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Shared PgQueryResolver Lambda entry point (B3.2b).
|
|
2
|
+
//
|
|
3
|
+
// One in-VPC Lambda serves every Postgres-backed read model's GraphQL Query
|
|
4
|
+
// fields (they have no per-table AppSync data source). At cold start it reads
|
|
5
|
+
// QUERY_RESOLVER_CONFIG (one shared pgConnection + one handler per read model),
|
|
6
|
+
// dynamically imports each spec package, and registers a per-read-model
|
|
7
|
+
// `binding` (ops + QueryEnginePostgres push-downs + indexes/subIdField/capability
|
|
8
|
+
// + baked labelField/includeIdParam + authorization) into
|
|
9
|
+
// PgQueryResolver_Lambda. AppSync invokes `handler` with the resolver payload
|
|
10
|
+
// { readModelName, kind, index?, arguments, identity }, which
|
|
11
|
+
// PgQueryResolver_Lambda.dispatch routes.
|
|
12
|
+
//
|
|
13
|
+
// ReScript labeled args compile positionally, so the engine push-downs
|
|
14
|
+
// (indexLookup/byIds/listPage/scan) are assigned straight into the `pushdowns`
|
|
15
|
+
// record — same calling convention PgQueryResolver_Lambda.dispatch invokes them
|
|
16
|
+
// with (verified against the compiled QueryEnginePostgres output).
|
|
17
|
+
|
|
18
|
+
import { poolFor } from "@reventlessdev/reventless-aws/src/adapter/Postgres/PgRuntime.res.mjs";
|
|
19
|
+
import { Make as makeEngine } from "@reventlessdev/reventless-postgres/src/QueryEnginePostgres.res.mjs";
|
|
20
|
+
import { opsFor as pgQdbOpsFor } from "@reventlessdev/reventless-aws/src/adapter/QueryDb/QueryDbStorage_Postgres_Runtime.res.mjs";
|
|
21
|
+
import { deriveServerCapability } from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
22
|
+
import { register, registerNodeType, handler as dispatchHandler } from "@reventlessdev/reventless-aws/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs";
|
|
23
|
+
import { patchSpecId, log } from "./HandlerFactoryHelpers.mjs";
|
|
24
|
+
|
|
25
|
+
const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
|
|
26
|
+
|
|
27
|
+
// A bounded full-materialisation for the list fallback (shapes listPage
|
|
28
|
+
// declines). Kept high; the fallback is only hit for search/searchPrefix/ids/
|
|
29
|
+
// backward pagination, which the AutoUI does not issue for large models.
|
|
30
|
+
const SCAN_ALL_LIMIT = 100000;
|
|
31
|
+
|
|
32
|
+
async function buildAllBindings() {
|
|
33
|
+
const configStr = process.env["QUERY_RESOLVER_CONFIG"] || '{"handlers":[]}';
|
|
34
|
+
const config = JSON.parse(configStr);
|
|
35
|
+
const pgConnection = config.pgConnection;
|
|
36
|
+
if (!pgConnection) {
|
|
37
|
+
log.warn("no pgConnection in QUERY_RESOLVER_CONFIG", { comp: "PgQueryResolver" });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Container-lifetime pool (memoised by poolFor); the engine and the ops set
|
|
41
|
+
// share it, so a Lambda holds one pool regardless of read model count.
|
|
42
|
+
const pool = poolFor(pgConnection);
|
|
43
|
+
const engine = makeEngine({ pool });
|
|
44
|
+
|
|
45
|
+
// Relay node type → read-model map (B3.2c), baked into the config.
|
|
46
|
+
for (const [typeName, rm] of Object.entries(config.nodeTypes || {})) {
|
|
47
|
+
registerNodeType(typeName, rm);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const pushdowns = {
|
|
51
|
+
indexLookup: engine.indexLookup,
|
|
52
|
+
byIds: engine.byIds,
|
|
53
|
+
listPage: engine.listPage,
|
|
54
|
+
itemsPage: engine.itemsPage,
|
|
55
|
+
scanAll: (readModelName) => engine.scan(readModelName, [], SCAN_ALL_LIMIT),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
await Promise.all((config.handlers || []).map(async h => {
|
|
59
|
+
const specModule = patchSpecId(await dynamicImport(h.specModule));
|
|
60
|
+
const indexes = (specModule.config && specModule.config.indexes) || [];
|
|
61
|
+
const subIdField = specModule.subIdConfig ? specModule.subIdConfig.subIdField : undefined;
|
|
62
|
+
const capability = deriveServerCapability(specModule.stateSchema);
|
|
63
|
+
const ops = pgQdbOpsFor(pgConnection, h.readModelName, indexes, subIdField);
|
|
64
|
+
|
|
65
|
+
register(h.readModelName, {
|
|
66
|
+
ops,
|
|
67
|
+
pushdowns,
|
|
68
|
+
indexes,
|
|
69
|
+
subIdField,
|
|
70
|
+
capability,
|
|
71
|
+
labelField: h.labelField,
|
|
72
|
+
includeIdParam: h.includeIdParam,
|
|
73
|
+
// Compiled string variant (e.g. "AllowAuthenticated"), injected on the
|
|
74
|
+
// spec by @@reventless.spec — the shape Reventless.Authorization.isAllowed
|
|
75
|
+
// matches on.
|
|
76
|
+
authorization: specModule.authorization,
|
|
77
|
+
});
|
|
78
|
+
log.debug("registered resolver binding for " + h.readModelName, { comp: "PgQueryResolver" });
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const initPromise = buildAllBindings();
|
|
83
|
+
|
|
84
|
+
// AppSync Lambda data source: the invocation payload IS the event.
|
|
85
|
+
export async function handler(event, context) {
|
|
86
|
+
await initPromise;
|
|
87
|
+
return dispatchHandler(event, context);
|
|
88
|
+
}
|
|
@@ -8,6 +8,7 @@ import { tag as requestContextTag } from "@reventlessdev/reventless-core/src/Req
|
|
|
8
8
|
import { Make as readModelCallbackMake } from "@reventlessdev/reventless-core/src/components/ReadModel/ReadModel_Callback.res.mjs";
|
|
9
9
|
import { load as qdbLoad, loadStream as qdbLoadStream, save as qdbSave, saveBatch as qdbSaveBatch, count as qdbCount, $$delete as qdbDelete, deleteBatch as qdbDeleteBatch } from "@reventlessdev/reventless-aws/src/adapter/QueryDb/QueryDbStorage_DynamoDb_Runtime.res.mjs";
|
|
10
10
|
import { opsFor as pgQdbOpsFor } from "@reventlessdev/reventless-aws/src/adapter/QueryDb/QueryDbStorage_Postgres_Runtime.res.mjs";
|
|
11
|
+
import { withLiveUpdates } from "./StateTopicPublish.mjs";
|
|
11
12
|
import { handleStreamEvent } from "@reventlessdev/reventless-aws/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res.mjs";
|
|
12
13
|
|
|
13
14
|
const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
|
|
@@ -74,17 +75,28 @@ function groupBySource(records) {
|
|
|
74
75
|
// runtime builder injected for a Postgres-backed read model: bind the Postgres
|
|
75
76
|
// operation set (queryDbTableName is then the read-model spec name, the shared
|
|
76
77
|
// `qdb_<name>` discriminator). Absent → the DynamoDB path is byte-identical.
|
|
77
|
-
function buildReadModelHandler(specModule, mappingsModule, queryDbTableName, pgConnection) {
|
|
78
|
+
function buildReadModelHandler(specModule, mappingsModule, queryDbTableName, pgConnection, stateTopicName) {
|
|
78
79
|
const patchedSpec = patchSpecId(specModule);
|
|
79
80
|
let operations;
|
|
80
81
|
if (pgConnection) {
|
|
81
82
|
const indexes = (patchedSpec.config && patchedSpec.config.indexes) || [];
|
|
82
83
|
const subIdField = patchedSpec.subIdConfig ? patchedSpec.subIdConfig.subIdField : undefined;
|
|
83
84
|
const pgOps = pgQdbOpsFor(pgConnection, queryDbTableName, indexes, subIdField);
|
|
85
|
+
// B3.3: on a subscription-enabled (Stream) Postgres read model, publish a
|
|
86
|
+
// live-update descriptor after each save/delete (no DynamoDB stream exists).
|
|
87
|
+
// `stateTopicName` (present only for stream RMs) + APPSYNC_ENDPOINT gate it;
|
|
88
|
+
// absent → withLiveUpdates returns pgOps unchanged. Wrap BEFORE mkInjectIdSave
|
|
89
|
+
// so the publish sees the id-injected state (id, subId, updatedAt).
|
|
90
|
+
const livePgOps = withLiveUpdates(pgOps, {
|
|
91
|
+
endpoint: process.env.APPSYNC_ENDPOINT,
|
|
92
|
+
region: process.env.AWS_REGION,
|
|
93
|
+
topicName: stateTopicName,
|
|
94
|
+
subIdField,
|
|
95
|
+
});
|
|
84
96
|
operations = {
|
|
85
|
-
...
|
|
86
|
-
save: mkInjectIdSave(
|
|
87
|
-
saveBatch: mkInjectIdSaveBatch(
|
|
97
|
+
...livePgOps,
|
|
98
|
+
save: mkInjectIdSave(livePgOps.save),
|
|
99
|
+
saveBatch: mkInjectIdSaveBatch(livePgOps.saveBatch),
|
|
88
100
|
};
|
|
89
101
|
} else {
|
|
90
102
|
const table = makeTableRef(queryDbTableName);
|
|
@@ -119,7 +131,7 @@ async function buildAllHandlers() {
|
|
|
119
131
|
await Promise.all(config.handlers.map(async h => {
|
|
120
132
|
const specModule = await dynamicImport(h.specModule);
|
|
121
133
|
const mappingsModule = await dynamicImport(h.mappingsModule);
|
|
122
|
-
const handler = buildReadModelHandler(specModule, mappingsModule, h.queryDbTableName, h.pgConnection);
|
|
134
|
+
const handler = buildReadModelHandler(specModule, mappingsModule, h.queryDbTableName, h.pgConnection, h.stateTopicName);
|
|
123
135
|
// Multiple read models can share one source stream — e.g. every admin read
|
|
124
136
|
// model (Plugins, PluginHistory, PlatformEventGraph, UIFragmentRegistry)
|
|
125
137
|
// projects the Plugin aggregate's EventLog stream. Accumulate ALL handlers
|
|
@@ -180,6 +180,16 @@ let makeFromCodeAsset: (
|
|
|
180
180
|
variables->Dict.set(key, value)
|
|
181
181
|
})
|
|
182
182
|
|
|
183
|
+
// ESM self-containment (Option C): every code archive built by
|
|
184
|
+
// Util_Bundle.buildCodeArchive ships register-hook.mjs + layer-resolver.mjs at
|
|
185
|
+
// /var/task. --import registers the resolve hook so the ESM entry point (and the
|
|
186
|
+
// user spec/behavior modules it dynamically imports) can resolve bare specifiers
|
|
187
|
+
// — @reventlessdev/*, effect, sury from the layer, @aws-sdk/* from the runtime —
|
|
188
|
+
// that ESM `import` would otherwise not find outside /var/task. Framework
|
|
189
|
+
// invariant: set last so it wins over any caller-supplied value.
|
|
190
|
+
variables->Dict.set("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make)
|
|
191
|
+
variables->Dict.set("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make)
|
|
192
|
+
|
|
183
193
|
let tags = AWS.Tags.make(~name, ReventlessCore.CommandTopic.componentType)
|
|
184
194
|
let lambda = Lambda.Function.make(
|
|
185
195
|
~name,
|
|
@@ -13,6 +13,7 @@ import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
|
|
|
13
13
|
import * as Adapter$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Adapter.res.mjs";
|
|
14
14
|
import * as Runtime$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Runtime/Runtime.res.mjs";
|
|
15
15
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
16
|
+
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
16
17
|
import * as Util_Lambda$ReventlessAws from "../../util/Util_Lambda.res.mjs";
|
|
17
18
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
18
19
|
import * as CommandTopic$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic.res.mjs";
|
|
@@ -91,6 +92,8 @@ function makeFromCodeAsset(name, code, sourceCodeHash, envVarsOpt, memorySizeOpt
|
|
|
91
92
|
Stdlib_Dict.forEachWithKey(additionalEnvVars, (value, key) => {
|
|
92
93
|
variables[key] = value;
|
|
93
94
|
});
|
|
95
|
+
variables["NODE_OPTIONS"] = Util_Bundle$ReventlessAws.esmLoaderNodeOptions;
|
|
96
|
+
variables["ESM_FALLBACK_DIRS"] = Util_Bundle$ReventlessAws.esmFallbackDirs;
|
|
94
97
|
let tags = AWS_Tags$ReventlessAws.make(name, CommandTopic$ReventlessCore.componentType);
|
|
95
98
|
let lambda = new (Aws.lambda.Function)(name, {
|
|
96
99
|
handler: "index.handler",
|
|
@@ -95,20 +95,15 @@ let forDcbCommandTopic = (
|
|
|
95
95
|
// fragment merged into HANDLER_CONFIG below.
|
|
96
96
|
let pgConnectionFragment = switch pgSelection {
|
|
97
97
|
| Some(sel) =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
->Dict.fromArray
|
|
108
|
-
->JSON.Encode.object
|
|
109
|
-
->JSON.stringify
|
|
110
|
-
`,"pgConnection":${pgConnectionJson}`
|
|
111
|
-
})
|
|
98
|
+
// C2: the DCB command Lambda is the only Postgres path that takes an append
|
|
99
|
+
// lock, so `lockStrategy` rides in this fragment (not in `connectionConfig`,
|
|
100
|
+
// which the classic/QueryDb paths share). The entry point passes it to
|
|
101
|
+
// `DcbEventLogStorage_Postgres_Runtime.opsFor`.
|
|
102
|
+
sel.connectionConfig->Pulumi.Output.apply(cc =>
|
|
103
|
+
`,"pgConnection":${cc
|
|
104
|
+
->PgConnection.connectionConfigToJson(~lockStrategy=sel.lockStrategy)
|
|
105
|
+
->JSON.stringify}`
|
|
106
|
+
)
|
|
112
107
|
| None => Pulumi.Output.make("")
|
|
113
108
|
}
|
|
114
109
|
|
|
@@ -9,6 +9,7 @@ import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/co
|
|
|
9
9
|
import * as DcbBackend$ReventlessAws from "../DcbEventLog/DcbBackend.res.mjs";
|
|
10
10
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
11
11
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
12
|
+
import * as PgConnection$ReventlessAws from "../Postgres/PgConnection.res.mjs";
|
|
12
13
|
import * as CommandTopicChannel_SQS$ReventlessAws from "../CommandTopic/CommandTopicChannel_SQS.res.mjs";
|
|
13
14
|
import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
|
|
14
15
|
|
|
@@ -51,31 +52,7 @@ function forDcbCommandTopic(slicePaths, dcbTableName, pluginName, syncConfig, as
|
|
|
51
52
|
let b = Stdlib_Option.getOr(JSON.stringify(param[1]), `""`);
|
|
52
53
|
return `{"spec":` + s + `,"behavior":` + b + `}`;
|
|
53
54
|
}).join(",");
|
|
54
|
-
let pgConnectionFragment = pgSelection !== undefined ? pgSelection.connectionConfig.apply(cc =>
|
|
55
|
-
let pgConnectionJson = JSON.stringify(Object.fromEntries([
|
|
56
|
-
[
|
|
57
|
-
"host",
|
|
58
|
-
cc.host
|
|
59
|
-
],
|
|
60
|
-
[
|
|
61
|
-
"port",
|
|
62
|
-
cc.port
|
|
63
|
-
],
|
|
64
|
-
[
|
|
65
|
-
"database",
|
|
66
|
-
cc.database
|
|
67
|
-
],
|
|
68
|
-
[
|
|
69
|
-
"username",
|
|
70
|
-
cc.username
|
|
71
|
-
],
|
|
72
|
-
[
|
|
73
|
-
"secretArn",
|
|
74
|
-
cc.secretArn
|
|
75
|
-
]
|
|
76
|
-
]));
|
|
77
|
-
return `,"pgConnection":` + pgConnectionJson;
|
|
78
|
-
}) : Pulumi.output("");
|
|
55
|
+
let pgConnectionFragment = pgSelection !== undefined ? pgSelection.connectionConfig.apply(cc => `,"pgConnection":` + JSON.stringify(PgConnection$ReventlessAws.connectionConfigToJson(pgSelection.lockStrategy, cc))) : Pulumi.output("");
|
|
79
56
|
let handlerConfigJson = Pulumi.all([
|
|
80
57
|
dcbTableName$1,
|
|
81
58
|
queue.id,
|