@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
|
@@ -173,7 +173,7 @@ function finish() {
|
|
|
173
173
|
cmdTopicEnvVars["HANDLER_CONFIG"] = cmdTopicHandlerConfigOutput;
|
|
174
174
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
|
|
175
175
|
let cmdTopicName = baseName + "CmdHandler";
|
|
176
|
-
let cmdTopicRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdTopicName, match.code, match.sourceCodeHash, cmdTopicEnvVars, Math.max(spec.commandTopicMemorySize, 1024), Math.max(spec.commandTopicTimeout, 30), undefined, undefined, undefined, undefined, aggregateOpts);
|
|
176
|
+
let cmdTopicRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdTopicName, match.code, match.sourceCodeHash, cmdTopicEnvVars, Math.max(spec.commandTopicMemorySize, 1024), Math.max(spec.commandTopicTimeout, 30), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
|
|
177
177
|
spec.commandTopicConnects.forEach(connect => connect(cmdTopicRuntime));
|
|
178
178
|
if (spec.commandGeneratorConnects.length !== 0) {
|
|
179
179
|
let cmdGenHandlerConfigOutput = Pulumi.all([
|
|
@@ -185,7 +185,7 @@ function finish() {
|
|
|
185
185
|
cmdGenEnvVars["HANDLER_CONFIG"] = cmdGenHandlerConfigOutput;
|
|
186
186
|
let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
|
|
187
187
|
let cmdGenName = baseName + "CmdGen";
|
|
188
|
-
let cmdGenRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdGenName, match$1.code, match$1.sourceCodeHash, cmdGenEnvVars, Math.max(spec.commandGeneratorMemorySize, 1024), Math.max(spec.commandGeneratorTimeout, 30), undefined, undefined, undefined, undefined, aggregateOpts);
|
|
188
|
+
let cmdGenRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdGenName, match$1.code, match$1.sourceCodeHash, cmdGenEnvVars, Math.max(spec.commandGeneratorMemorySize, 1024), Math.max(spec.commandGeneratorTimeout, 30), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
|
|
189
189
|
spec.commandGeneratorConnects.forEach(connect => connect(cmdGenRuntime));
|
|
190
190
|
}
|
|
191
191
|
let match$2 = spec.eventCollectorChannelSpec;
|
|
@@ -208,7 +208,7 @@ function finish() {
|
|
|
208
208
|
evtMapperPackageDirs[mappingsPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(mappingsPkg);
|
|
209
209
|
let match$4 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/EventMapperEntryPoint.mjs", evtMapperPackageDirs, undefined);
|
|
210
210
|
let evtMapperName = baseName + "EventMapper";
|
|
211
|
-
let evtMapperRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(evtMapperName, match$4.code, match$4.sourceCodeHash, evtMapperEnvVars, Math.max(spec.eventCollectorMemorySize, 2048), Math.max(spec.eventCollectorTimeout, 180), undefined, undefined, undefined, undefined, aggregateOpts);
|
|
211
|
+
let evtMapperRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(evtMapperName, match$4.code, match$4.sourceCodeHash, evtMapperEnvVars, Math.max(spec.eventCollectorMemorySize, 2048), Math.max(spec.eventCollectorTimeout, 180), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
|
|
212
212
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect(evtMapperName, [match$2], evtMapperRuntime, aggregateOpts);
|
|
213
213
|
});
|
|
214
214
|
}
|
|
@@ -173,7 +173,7 @@ function finish() {
|
|
|
173
173
|
cmdTopicEnvVars["HANDLER_CONFIG"] = cmdTopicHandlerConfigOutput;
|
|
174
174
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
|
|
175
175
|
let cmdTopicName = baseName + "CmdHandler";
|
|
176
|
-
let cmdTopicRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdTopicName, match.code, match.sourceCodeHash, cmdTopicEnvVars, Math.max(spec.commandTopicMemorySize, 1024), Math.max(spec.commandTopicTimeout, 30), undefined, undefined, undefined, undefined, aggregateOpts);
|
|
176
|
+
let cmdTopicRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdTopicName, match.code, match.sourceCodeHash, cmdTopicEnvVars, Math.max(spec.commandTopicMemorySize, 1024), Math.max(spec.commandTopicTimeout, 30), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
|
|
177
177
|
spec.commandTopicConnects.forEach(connect => connect(cmdTopicRuntime));
|
|
178
178
|
if (spec.commandGeneratorConnects.length !== 0) {
|
|
179
179
|
let cmdGenHandlerConfigOutput = Pulumi.all([
|
|
@@ -186,7 +186,7 @@ function finish() {
|
|
|
186
186
|
cmdGenEnvVars["DISPATCH_MODE"] = "async";
|
|
187
187
|
let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
|
|
188
188
|
let cmdGenName = baseName + "CmdGen";
|
|
189
|
-
let cmdGenRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdGenName, match$1.code, match$1.sourceCodeHash, cmdGenEnvVars, Math.max(spec.commandGeneratorMemorySize, 1024), Math.max(spec.commandGeneratorTimeout, 30), undefined, undefined, undefined, undefined, aggregateOpts);
|
|
189
|
+
let cmdGenRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdGenName, match$1.code, match$1.sourceCodeHash, cmdGenEnvVars, Math.max(spec.commandGeneratorMemorySize, 1024), Math.max(spec.commandGeneratorTimeout, 30), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
|
|
190
190
|
spec.commandGeneratorConnects.forEach(connect => connect(cmdGenRuntime));
|
|
191
191
|
}
|
|
192
192
|
let match$2 = spec.eventCollectorChannelSpec;
|
|
@@ -209,7 +209,7 @@ function finish() {
|
|
|
209
209
|
evtMapperPackageDirs[mappingsPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(mappingsPkg);
|
|
210
210
|
let match$4 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/EventMapperEntryPoint.mjs", evtMapperPackageDirs, undefined);
|
|
211
211
|
let evtMapperName = baseName + "EventMapper";
|
|
212
|
-
let evtMapperRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(evtMapperName, match$4.code, match$4.sourceCodeHash, evtMapperEnvVars, Math.max(spec.eventCollectorMemorySize, 2048), Math.max(spec.eventCollectorTimeout, 180), undefined, undefined, undefined, undefined, aggregateOpts);
|
|
212
|
+
let evtMapperRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(evtMapperName, match$4.code, match$4.sourceCodeHash, evtMapperEnvVars, Math.max(spec.eventCollectorMemorySize, 2048), Math.max(spec.eventCollectorTimeout, 180), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
|
|
213
213
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect(evtMapperName, [match$2], evtMapperRuntime, aggregateOpts);
|
|
214
214
|
});
|
|
215
215
|
}
|
|
@@ -152,7 +152,7 @@ function finish() {
|
|
|
152
152
|
packageDirs[specPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(specPkg);
|
|
153
153
|
packageDirs[behaviorPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(behaviorPkg);
|
|
154
154
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
|
|
155
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(lambdaName, match.code, match.sourceCodeHash, envVars, spec.memorySize, spec.timeout, undefined, undefined, undefined, undefined, aggregateOpts);
|
|
155
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(lambdaName, match.code, match.sourceCodeHash, envVars, spec.memorySize, spec.timeout, undefined, undefined, undefined, undefined, undefined, aggregateOpts);
|
|
156
156
|
spec.connects.forEach(connect => connect(runtime));
|
|
157
157
|
let channelSpecs = Stdlib_Option.mapOr(spec.eventCollectorChannelSpec, [], cs => [cs]);
|
|
158
158
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect(name, channelSpecs, runtime, aggregateOpts);
|
|
@@ -152,7 +152,7 @@ function finish() {
|
|
|
152
152
|
packageDirs[specPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(specPkg);
|
|
153
153
|
packageDirs[behaviorPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(behaviorPkg);
|
|
154
154
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
|
|
155
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, spec.memorySize, spec.timeout, undefined, undefined, undefined, undefined, aggregateOpts);
|
|
155
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, spec.memorySize, spec.timeout, undefined, undefined, undefined, undefined, undefined, aggregateOpts);
|
|
156
156
|
spec.connects.forEach(connect => connect(runtime));
|
|
157
157
|
let channelSpecs = Stdlib_Option.mapOr(spec.eventCollectorChannelSpec, [], cs => [cs]);
|
|
158
158
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect(name, channelSpecs, runtime, aggregateOpts);
|
|
@@ -187,7 +187,7 @@ function finish() {
|
|
|
187
187
|
}));
|
|
188
188
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
|
|
189
189
|
Stdlib_Option.forEach(cfg.sqsBatchSize, CommandTopicChannel_SQS_Sync$ReventlessAws.setBatchSize);
|
|
190
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAggregatesCmdHandler", match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, undefined, opts);
|
|
190
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAggregatesCmdHandler", match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, undefined, undefined, opts);
|
|
191
191
|
let tableName$1 = pluginRmTableName.contents;
|
|
192
192
|
if (tableName$1 !== undefined) {
|
|
193
193
|
Pulumi.all([
|
|
@@ -174,7 +174,7 @@ function finish() {
|
|
|
174
174
|
}));
|
|
175
175
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
|
|
176
176
|
Stdlib_Option.forEach(cfg.sqsBatchSize, CommandTopicChannel_SQS_Async$ReventlessAws.setBatchSize);
|
|
177
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAggregatesAsyncCmdHandler", match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, undefined, opts);
|
|
177
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAggregatesAsyncCmdHandler", match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, undefined, undefined, opts);
|
|
178
178
|
specs.forEach(param => {
|
|
179
179
|
param.connects.forEach(connect => connect(runtime));
|
|
180
180
|
});
|
|
@@ -116,7 +116,7 @@ function finish() {
|
|
|
116
116
|
let envVars = {};
|
|
117
117
|
envVars["HANDLER_CONFIG"] = handlerConfigOutput;
|
|
118
118
|
let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AutomationSliceEntryPoint.mjs", packageDirs, undefined);
|
|
119
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAutomationSlices", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, opts);
|
|
119
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAutomationSlices", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, undefined, opts);
|
|
120
120
|
let channelSpecs = storedSpecs.map(param => param.channelSpec);
|
|
121
121
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllAutomationSlices", channelSpecs, runtime, opts);
|
|
122
122
|
} else {
|
|
@@ -22,6 +22,7 @@ import { makeGenerateCommand } from "@reventlessdev/reventless-core/src/componen
|
|
|
22
22
|
import { commandOutcomeToJson, runInlineAndCollect } from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Helpers.res.mjs";
|
|
23
23
|
import { json as jsonSchema } from "sury/src/S.res.mjs";
|
|
24
24
|
import { read, append, readStream } from "@reventlessdev/reventless-aws/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs";
|
|
25
|
+
import { opsFor as pgDcbEventLogOpsFor } from "@reventlessdev/reventless-aws/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres_Runtime.res.mjs";
|
|
25
26
|
import { handleQueueEvent, publishJsons as sqsPublishJsons } from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
26
27
|
|
|
27
28
|
const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
|
|
@@ -90,12 +91,25 @@ function getIdStringSchema() {
|
|
|
90
91
|
// can drive the real `cmdGenHandler` end-to-end against DynamoDB Local.
|
|
91
92
|
export async function buildHandlersForConfig(config, opts = {}) {
|
|
92
93
|
const loadModule = opts.loadModule ?? dynamicImport;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
// `pgConnection`, when present in HANDLER_CONFIG, selects the Postgres DCB
|
|
95
|
+
// runtime (dcbEventLogTableName doubles as the `dcb_event.log_name`); absence
|
|
96
|
+
// keeps the DynamoDB path byte-identical. NB: this swaps only the *storage*
|
|
97
|
+
// ops — event propagation (within-plugin projections + cross-plugin SNS) is
|
|
98
|
+
// stream-driven on AWS and is handled separately by the change-feed relay
|
|
99
|
+
// (see docs/plans/aws-postgres-change-feed-bridge.md), not by this Lambda.
|
|
100
|
+
const rawStorageOps = config.pgConnection
|
|
101
|
+
? (() => {
|
|
102
|
+
const pgOps = pgDcbEventLogOpsFor(config.pgConnection, config.dcbEventLogTableName);
|
|
103
|
+
return { read: pgOps.read, append: pgOps.append, readStream: pgOps.readStream };
|
|
104
|
+
})()
|
|
105
|
+
: (() => {
|
|
106
|
+
const resolvedTable = { name: config.dcbEventLogTableName };
|
|
107
|
+
return {
|
|
108
|
+
read: read(resolvedTable),
|
|
109
|
+
append: append(resolvedTable),
|
|
110
|
+
readStream: readStream(resolvedTable),
|
|
111
|
+
};
|
|
112
|
+
})();
|
|
99
113
|
|
|
100
114
|
const handlersByType = {};
|
|
101
115
|
const sharedDcbEventLogOps = dcbEventLogOperationsMake({
|
|
@@ -61,7 +61,7 @@ function forEventCollector(param, eventTopics, resources, memorySizeOpt, timeout
|
|
|
61
61
|
packageDirs[specPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(specPkg);
|
|
62
62
|
packageDirs[mappingsPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(mappingsPkg);
|
|
63
63
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/ReadModelEntryPoint.mjs", packageDirs, undefined);
|
|
64
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, opts);
|
|
64
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts);
|
|
65
65
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect(name, [{
|
|
66
66
|
channel: channel,
|
|
67
67
|
eventTopics: eventTopics,
|
|
@@ -108,7 +108,7 @@ function finish() {
|
|
|
108
108
|
let envVars = {};
|
|
109
109
|
envVars["HANDLER_CONFIG"] = handlerConfigOutput;
|
|
110
110
|
let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/ReadModelEntryPoint.mjs", packageDirs, undefined);
|
|
111
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllReadModels", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, opts);
|
|
111
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllReadModels", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, undefined, opts);
|
|
112
112
|
let channelSpecs = storedSpecs.map(param => param.channelSpec);
|
|
113
113
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllReadModels", channelSpecs, runtime, opts);
|
|
114
114
|
} else {
|
|
@@ -39,7 +39,7 @@ function forCommandTopic(param, connect, memorySizeOpt, timeoutOpt, specModuleUr
|
|
|
39
39
|
packageDirs[specPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(specPkg);
|
|
40
40
|
packageDirs[mappingsPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(mappingsPkg);
|
|
41
41
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/ExtensionPointEntryPoint.mjs", packageDirs, undefined);
|
|
42
|
-
return connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, opts));
|
|
42
|
+
return connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function finish() {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// PgChangeFeedRelay Lambda entry point (scheduled poll — B2.3).
|
|
2
|
+
//
|
|
3
|
+
// Triggered on a schedule (EventBridge rate rule). Each invocation drains every
|
|
4
|
+
// Postgres DCB log listed in HANDLER_CONFIG from its checkpoint and relays the
|
|
5
|
+
// events, transformed into the {id, meta, event} shape, onto the plugin
|
|
6
|
+
// EventCollector SQS queue — which then fans out to projections, aggregate
|
|
7
|
+
// command topics, and the cross-plugin SNS EventTopic.
|
|
8
|
+
// See docs/plans/aws-postgres-change-feed-bridge.md.
|
|
9
|
+
//
|
|
10
|
+
// HANDLER_CONFIG shape:
|
|
11
|
+
// { "logs": [ { "pgConnection": {host,port,database,username,secretArn},
|
|
12
|
+
// "logName": string, // dcb_event.log_name
|
|
13
|
+
// "subscriber": string, // dcb_subscription checkpoint key
|
|
14
|
+
// "targetQueueUrl": string, // plugin EventCollector SQS URL
|
|
15
|
+
// "partitionTag"?: <derivedPartitionTag> } ] }
|
|
16
|
+
|
|
17
|
+
import { relay } from "@reventlessdev/reventless-aws/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs";
|
|
18
|
+
import { sendMessage } from "@reventlessdev/reventless-aws/src/util/Util_SQS_Runtime.res.mjs";
|
|
19
|
+
import { makeQueueRef, log } from "./HandlerFactoryHelpers.mjs";
|
|
20
|
+
|
|
21
|
+
// Standard (non-FIFO) EventCollector queue — no message group id needed. The
|
|
22
|
+
// SQS handler parses each body straight back to JSON (Util_SQS_Runtime.parseSqsRecord).
|
|
23
|
+
function makeSendBatch(targetQueueUrl) {
|
|
24
|
+
const queue = makeQueueRef(targetQueueUrl);
|
|
25
|
+
return (jsons) =>
|
|
26
|
+
Promise.all(jsons.map((json) => sendMessage(queue, undefined, JSON.stringify(json)))).then(
|
|
27
|
+
() => {}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function handler(_event, _context) {
|
|
32
|
+
const configStr = process.env["HANDLER_CONFIG"] || '{"logs":[]}';
|
|
33
|
+
const config = JSON.parse(configStr);
|
|
34
|
+
const logs = config.logs || [];
|
|
35
|
+
|
|
36
|
+
await Promise.all(
|
|
37
|
+
logs.map(async (l) => {
|
|
38
|
+
const sendBatch = makeSendBatch(l.targetQueueUrl);
|
|
39
|
+
try {
|
|
40
|
+
const count = await relay(l.pgConnection, l.logName, l.subscriber, l.partitionTag, sendBatch);
|
|
41
|
+
log.debug("relayed " + count + " event(s) for " + l.logName, { comp: "PgChangeFeedRelay" });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
// Log and continue — a failed log this tick is retried next tick from its
|
|
44
|
+
// unchanged checkpoint (at-least-once; projections are idempotent).
|
|
45
|
+
log.warn("relay failed for " + l.logName + ": " + (err?.message ?? String(err)), {
|
|
46
|
+
comp: "PgChangeFeedRelay",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
@@ -78,6 +78,11 @@ let makeFromCodeAsset: (
|
|
|
78
78
|
Lambda's log group (command-handler Lambdas only). Requires a managed log
|
|
79
79
|
group, so it only takes effect when `~logRetentionDays` is also set. */
|
|
80
80
|
~dcbMetrics: bool=?,
|
|
81
|
+
/** Place the Lambda in a VPC (needed to reach RDS/managed Postgres). When set,
|
|
82
|
+
the execution role also gets the EC2 network-interface permissions AWS
|
|
83
|
+
requires for VPC Lambdas. Build it from `PgConnection.{securityGroupId,
|
|
84
|
+
subnetIds}`. */
|
|
85
|
+
~vpcConfig: Pulumi.Input.t<PulumiAws.Lambda.Function.vpcConfig>=?,
|
|
81
86
|
~opts: Pulumi.ComponentResource.options=?,
|
|
82
87
|
) => ReventlessCore.Runtime.environment<parts> = (
|
|
83
88
|
~name,
|
|
@@ -90,6 +95,7 @@ let makeFromCodeAsset: (
|
|
|
90
95
|
~ephemeralStorageMb=?,
|
|
91
96
|
~logRetentionDays=?,
|
|
92
97
|
~dcbMetrics=false,
|
|
98
|
+
~vpcConfig=?,
|
|
93
99
|
~opts=?,
|
|
94
100
|
) => {
|
|
95
101
|
open PulumiAws
|
|
@@ -102,6 +108,37 @@ let makeFromCodeAsset: (
|
|
|
102
108
|
~opts?,
|
|
103
109
|
)
|
|
104
110
|
|
|
111
|
+
// VPC Lambdas must be able to manage ENIs in the target subnets. Attach the
|
|
112
|
+
// standard EC2 network-interface permissions (resource "*", as ENIs have no
|
|
113
|
+
// predictable ARN) whenever the function is VPC-placed.
|
|
114
|
+
vpcConfig->Option.forEach(_ => {
|
|
115
|
+
let _ = PulumiAws.IAM.RolePolicy.make(
|
|
116
|
+
~name=`${name}VpcAccess`,
|
|
117
|
+
~args={
|
|
118
|
+
policy: PolicyDocument.make(
|
|
119
|
+
~id=`${name}VpcAccessPolicy`,
|
|
120
|
+
~statements=[
|
|
121
|
+
{
|
|
122
|
+
sid: "AllowVpcEni",
|
|
123
|
+
effect: Allow,
|
|
124
|
+
actions: Actions([
|
|
125
|
+
"ec2:CreateNetworkInterface",
|
|
126
|
+
"ec2:DescribeNetworkInterfaces",
|
|
127
|
+
"ec2:DeleteNetworkInterface",
|
|
128
|
+
"ec2:AssignPrivateIpAddresses",
|
|
129
|
+
"ec2:UnassignPrivateIpAddresses",
|
|
130
|
+
]),
|
|
131
|
+
resources: Resource("*"),
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
)
|
|
135
|
+
->PolicyDocument.toJsonString
|
|
136
|
+
->Pulumi.Input.make,
|
|
137
|
+
role: lambdaRole.id->Pulumi.Output.asInput,
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
105
142
|
// Create additional IAM policies registered by consumers.
|
|
106
143
|
additionalIamPolicies->Array.forEach(({suffix, actions, resourceArn}) => {
|
|
107
144
|
let _ = resourceArn->Pulumi.Output.apply(arn => {
|
|
@@ -160,6 +197,7 @@ let makeFromCodeAsset: (
|
|
|
160
197
|
),
|
|
161
198
|
layers,
|
|
162
199
|
tags,
|
|
200
|
+
vpcConfig: ?vpcConfig,
|
|
163
201
|
environment: ({Lambda.Function.variables: variables}: Lambda.Function.functionEnvironment)
|
|
164
202
|
->Pulumi.Input.make,
|
|
165
203
|
},
|
|
@@ -41,13 +41,30 @@ function make(name, handler, memorySizeOpt, timeoutOpt, opts) {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function makeFromCodeAsset(name, code, sourceCodeHash, envVarsOpt, memorySizeOpt, timeoutOpt, reservedConcurrency, ephemeralStorageMb, logRetentionDays, dcbMetricsOpt, opts) {
|
|
44
|
+
function makeFromCodeAsset(name, code, sourceCodeHash, envVarsOpt, memorySizeOpt, timeoutOpt, reservedConcurrency, ephemeralStorageMb, logRetentionDays, dcbMetricsOpt, vpcConfig, opts) {
|
|
45
45
|
let envVars = envVarsOpt !== undefined ? envVarsOpt : ({});
|
|
46
46
|
let memorySize = memorySizeOpt !== undefined ? memorySizeOpt : Runtime$ReventlessCore.CommandHandlerDefaults.memorySize;
|
|
47
47
|
let timeout = timeoutOpt !== undefined ? timeoutOpt : Runtime$ReventlessCore.CommandHandlerDefaults.timeout;
|
|
48
48
|
let dcbMetrics = dcbMetricsOpt !== undefined ? dcbMetricsOpt : false;
|
|
49
49
|
let opts$1 = Stdlib_Option.map(opts, Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions);
|
|
50
50
|
let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name, Pulumi.output(AWS$ReventlessAws.Lambda.principal), opts$1);
|
|
51
|
+
Stdlib_Option.forEach(vpcConfig, param => {
|
|
52
|
+
new (Aws.iam.RolePolicy)(name + `VpcAccess`, {
|
|
53
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `VpcAccessPolicy`, [{
|
|
54
|
+
Sid: "AllowVpcEni",
|
|
55
|
+
Effect: "Allow",
|
|
56
|
+
Action: [
|
|
57
|
+
"ec2:CreateNetworkInterface",
|
|
58
|
+
"ec2:DescribeNetworkInterfaces",
|
|
59
|
+
"ec2:DeleteNetworkInterface",
|
|
60
|
+
"ec2:AssignPrivateIpAddresses",
|
|
61
|
+
"ec2:UnassignPrivateIpAddresses"
|
|
62
|
+
],
|
|
63
|
+
Resource: "*"
|
|
64
|
+
}])),
|
|
65
|
+
role: lambdaRole.id
|
|
66
|
+
});
|
|
67
|
+
});
|
|
51
68
|
additionalIamPolicies.forEach(param => {
|
|
52
69
|
let actions = param.actions;
|
|
53
70
|
let suffix = param.suffix;
|
|
@@ -91,7 +108,8 @@ function makeFromCodeAsset(name, code, sourceCodeHash, envVarsOpt, memorySizeOpt
|
|
|
91
108
|
reservedConcurrentExecutions: Stdlib_Option.map(reservedConcurrency, prim => prim),
|
|
92
109
|
ephemeralStorage: Stdlib_Option.map(ephemeralStorageMb, mb => ({
|
|
93
110
|
size: mb
|
|
94
|
-
}))
|
|
111
|
+
})),
|
|
112
|
+
vpcConfig: vpcConfig
|
|
95
113
|
}, opts$1 !== undefined ? Primitive_option.valFromOption(opts$1) : undefined);
|
|
96
114
|
Stdlib_Option.forEach(logRetentionDays, days => {
|
|
97
115
|
let logGroup = new (Aws.cloudwatch.LogGroup)(name + `LogGroup`, {
|
|
@@ -100,7 +100,7 @@ function finish() {
|
|
|
100
100
|
let envVars = {};
|
|
101
101
|
envVars["HANDLER_CONFIG"] = handlerConfigOutput;
|
|
102
102
|
let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/SideEffectEntryPoint.mjs", packageDirs, undefined);
|
|
103
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllSideEffectHandlers", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, opts);
|
|
103
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllSideEffectHandlers", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, undefined, opts);
|
|
104
104
|
let channelSpecs = storedSpecs.map(param => param.channelSpec);
|
|
105
105
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllSideEffectHandlers", channelSpecs, runtime, opts);
|
|
106
106
|
} else {
|
|
@@ -66,7 +66,7 @@ function forDcbCommandTopic(slicePaths, dcbTableName, pluginName, syncConfig, as
|
|
|
66
66
|
packageDirs["@reventlessdev/reventless-core"] = Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-core");
|
|
67
67
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs", packageDirs, undefined);
|
|
68
68
|
Stdlib_Option.forEach(cfg.sqsBatchSize, CommandTopicChannel_SQS$ReventlessAws.setBatchSize);
|
|
69
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, true, opts);
|
|
69
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, true, undefined, opts);
|
|
70
70
|
queue.arn.apply(queueArn => new (Aws.iam.RolePolicy)(name + `-sqsSend`, {
|
|
71
71
|
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `-sqsSendPolicy`, [{
|
|
72
72
|
Sid: "AllowSqsSend",
|
|
@@ -117,7 +117,7 @@ function buildLambda(parent, handlerOutputs, packageDirs, channelSpecs, memorySi
|
|
|
117
117
|
packageDirs["@reventlessdev/reventless-aws"] = Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-aws");
|
|
118
118
|
packageDirs["@reventlessdev/reventless-core"] = Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-core");
|
|
119
119
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/StateViewSliceEntryPoint.mjs", packageDirs, undefined);
|
|
120
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllStateViewSlices", match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, opts);
|
|
120
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllStateViewSlices", match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts);
|
|
121
121
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllStateViewSlices", channelSpecs, runtime, opts);
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -29,7 +29,7 @@ function forBucketCallback(param, connect, memorySizeOpt, timeoutOpt, name, call
|
|
|
29
29
|
let pkg = Util_Bundle$ReventlessAws.extractPackageName(callbackModulePath);
|
|
30
30
|
packageDirs[pkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(pkg);
|
|
31
31
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/TaskBucketEntryPoint.mjs", packageDirs, undefined);
|
|
32
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(fullName, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, {
|
|
32
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(fullName, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, {
|
|
33
33
|
parent: resource
|
|
34
34
|
});
|
|
35
35
|
if (schedulerConfig !== undefined) {
|
|
@@ -266,6 +266,21 @@ let getClient = () =>
|
|
|
266
266
|
// reaching request is already authenticated). `AllowGroups([])` and
|
|
267
267
|
// `DenyAll` emit a sentinel `__deny_all__` group that no Cognito user can
|
|
268
268
|
// belong to — effectively blocking the field at the API layer.
|
|
269
|
+
//
|
|
270
|
+
// ── Dual-auth (Cognito + IAM) for deploy-time system callers ────────────────
|
|
271
|
+
// A field flagged `iamCallable` (via `mutationSchemaEntry`/`querySchemaEntry`)
|
|
272
|
+
// must be reachable by BOTH the console UI (Cognito) and a deploy-time system
|
|
273
|
+
// caller signing SigV4 with ambient AWS credentials (`Util_AppSync_Caller`,
|
|
274
|
+
// the AWS_IAM additional auth provider). `@aws_auth(...)` is the single-mode
|
|
275
|
+
// directive form and does not admit IAM on a multi-auth API. For these fields
|
|
276
|
+
// we emit the multi-auth form instead:
|
|
277
|
+
// @aws_cognito_user_pools(cognito_groups: [...]) @aws_iam
|
|
278
|
+
// The Cognito arm preserves the field's existing group gating (or, when the
|
|
279
|
+
// field carries no Cognito group restriction, a bare `@aws_cognito_user_pools`
|
|
280
|
+
// keeps it open to any authenticated Cognito user); the `@aws_iam` arm admits
|
|
281
|
+
// the system caller. IAM access must be constrained by the API resource policy
|
|
282
|
+
// and a least-privilege deploy-role policy — see
|
|
283
|
+
// `docs/guides/appsync-iam-system-caller.md`.
|
|
269
284
|
|
|
270
285
|
let _permissionToCognitoGroups = (
|
|
271
286
|
permission: Reventless.Authorization.permission,
|
|
@@ -282,6 +297,20 @@ let _formatGroupsDirective = (groups: array<string>): string => {
|
|
|
282
297
|
`@aws_auth(cognito_groups: [${quoted}])`
|
|
283
298
|
}
|
|
284
299
|
|
|
300
|
+
// Multi-auth directive for a field that must accept BOTH Cognito and IAM.
|
|
301
|
+
// `groups=Some([...])` preserves Cognito group gating; `groups=None` keeps the
|
|
302
|
+
// field open to any authenticated Cognito user. `@aws_iam` admits the
|
|
303
|
+
// deploy-time SigV4 system caller. See the dual-auth note above.
|
|
304
|
+
let _formatDualAuthDirective = (groups: option<array<string>>): string => {
|
|
305
|
+
let cognito = switch groups {
|
|
306
|
+
| Some(g) =>
|
|
307
|
+
let quoted = g->Array.map(x => `"${x}"`)->Array.join(", ")
|
|
308
|
+
`@aws_cognito_user_pools(cognito_groups: [${quoted}])`
|
|
309
|
+
| None => `@aws_cognito_user_pools`
|
|
310
|
+
}
|
|
311
|
+
`${cognito} @aws_iam`
|
|
312
|
+
}
|
|
313
|
+
|
|
285
314
|
let injectAwsAuth = (
|
|
286
315
|
fragment: Reventless.Plugin.apiSchemaFragment,
|
|
287
316
|
~mutationEntries: array<ReventlessInfra.Api.mutationSchemaEntry>,
|
|
@@ -292,8 +321,16 @@ let injectAwsAuth = (
|
|
|
292
321
|
// Build authorization lookup from mutation entries: fieldName -> groups.
|
|
293
322
|
// Spec-level `fieldPermissions` takes precedence over the legacy
|
|
294
323
|
// `authorization.group` per-entry single-group form.
|
|
324
|
+
// Fields opted into deploy-time IAM invocation (dual-auth). Membership here
|
|
325
|
+
// switches a field from the single-mode `@aws_auth` form to the multi-auth
|
|
326
|
+
// `@aws_cognito_user_pools(...) @aws_iam` form.
|
|
327
|
+
let iamFields: Dict.t<bool> = Dict.make()
|
|
328
|
+
|
|
295
329
|
let mutationAuthMap: Dict.t<array<string>> = Dict.make()
|
|
296
330
|
mutationEntries->Array.forEach(entry => {
|
|
331
|
+
if entry.iamCallable->Option.getOr(false) {
|
|
332
|
+
entry.fieldNames->Array.forEach(fieldName => iamFields->Dict.set(fieldName, true))
|
|
333
|
+
}
|
|
297
334
|
switch entry.authorization {
|
|
298
335
|
| Some({group}) =>
|
|
299
336
|
entry.fieldNames->Array.forEach(fieldName =>
|
|
@@ -319,6 +356,10 @@ let injectAwsAuth = (
|
|
|
319
356
|
// Spec-level `permission` takes precedence over the legacy authorization.
|
|
320
357
|
let queryAuthMap: Dict.t<array<string>> = Dict.make()
|
|
321
358
|
queryEntries->Array.forEach(entry => {
|
|
359
|
+
if entry.iamCallable->Option.getOr(false) {
|
|
360
|
+
iamFields->Dict.set(entry.singleFieldName, true)
|
|
361
|
+
iamFields->Dict.set(entry.listFieldName, true)
|
|
362
|
+
}
|
|
322
363
|
switch entry.authorization {
|
|
323
364
|
| Some({group}) =>
|
|
324
365
|
queryAuthMap->Dict.set(entry.singleFieldName, [group])
|
|
@@ -341,17 +382,27 @@ let injectAwsAuth = (
|
|
|
341
382
|
|
|
342
383
|
let augmentedMutations = parts.mutations->Array.map(field => {
|
|
343
384
|
let fieldName = ReventlessCore.GraphQL_Stitcher.extractLeadingName(field)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
385
|
+
let groups = mutationAuthMap->Dict.get(fieldName)
|
|
386
|
+
if iamFields->Dict.get(fieldName)->Option.getOr(false) {
|
|
387
|
+
`${field}\n ${_formatDualAuthDirective(groups)}`
|
|
388
|
+
} else {
|
|
389
|
+
switch groups {
|
|
390
|
+
| Some(groups) => `${field}\n ${_formatGroupsDirective(groups)}`
|
|
391
|
+
| None => field
|
|
392
|
+
}
|
|
347
393
|
}
|
|
348
394
|
})
|
|
349
395
|
|
|
350
396
|
let augmentedQueries = parts.queries->Array.map(field => {
|
|
351
397
|
let fieldName = ReventlessCore.GraphQL_Stitcher.extractLeadingName(field)
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
398
|
+
let groups = queryAuthMap->Dict.get(fieldName)
|
|
399
|
+
if iamFields->Dict.get(fieldName)->Option.getOr(false) {
|
|
400
|
+
`${field} ${_formatDualAuthDirective(groups)}`
|
|
401
|
+
} else {
|
|
402
|
+
switch groups {
|
|
403
|
+
| Some(groups) => `${field} ${_formatGroupsDirective(groups)}`
|
|
404
|
+
| None => field
|
|
405
|
+
}
|
|
355
406
|
}
|
|
356
407
|
})
|
|
357
408
|
|
|
@@ -374,17 +425,30 @@ let injectAwsAuth = (
|
|
|
374
425
|
// Injects @aws_auth with the given group on ALL mutation, query, and subscription
|
|
375
426
|
// fields in a fragment. Used for the base fragment where all fields share the
|
|
376
427
|
// same authorization group.
|
|
428
|
+
//
|
|
429
|
+
// `~iamFieldNames` opts the named mutation/query fields into deploy-time IAM
|
|
430
|
+
// (dual-auth): those fields emit `@aws_cognito_user_pools(cognito_groups:
|
|
431
|
+
// ["<group>"]) @aws_iam` instead of the single-mode `@aws_auth(...)`, keeping
|
|
432
|
+
// the same Cognito group gating while also admitting the SigV4 system caller.
|
|
433
|
+
// Subscriptions are never IAM-marked (the deploy caller does not subscribe).
|
|
377
434
|
let injectAwsAuthAll = (
|
|
378
435
|
fragment: Reventless.Plugin.apiSchemaFragment,
|
|
379
436
|
~group: string,
|
|
437
|
+
~iamFieldNames: array<string>=[],
|
|
380
438
|
): Reventless.Plugin.apiSchemaFragment => {
|
|
381
439
|
let parts = ReventlessCore.GraphQL_Stitcher.decode(fragment)
|
|
440
|
+
let isIam = (field: string): bool =>
|
|
441
|
+
iamFieldNames->Array.includes(ReventlessCore.GraphQL_Stitcher.extractLeadingName(field))
|
|
382
442
|
|
|
383
443
|
let augmentedMutations = parts.mutations->Array.map(field =>
|
|
384
|
-
|
|
444
|
+
isIam(field)
|
|
445
|
+
? `${field}\n ${_formatDualAuthDirective(Some([group]))}`
|
|
446
|
+
: `${field}\n @aws_auth(cognito_groups: ["${group}"])`
|
|
385
447
|
)
|
|
386
448
|
let augmentedQueries = parts.queries->Array.map(field =>
|
|
387
|
-
|
|
449
|
+
isIam(field)
|
|
450
|
+
? `${field} ${_formatDualAuthDirective(Some([group]))}`
|
|
451
|
+
: `${field} @aws_auth(cognito_groups: ["${group}"])`
|
|
388
452
|
)
|
|
389
453
|
let augmentedSubscriptions = parts.subscriptions->Array.map(field =>
|
|
390
454
|
`${field}\n @aws_auth(cognito_groups: ["${group}"])`
|
|
@@ -204,10 +204,27 @@ function _formatGroupsDirective(groups) {
|
|
|
204
204
|
return `@aws_auth(cognito_groups: [` + quoted + `])`;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
function _formatDualAuthDirective(groups) {
|
|
208
|
+
let cognito;
|
|
209
|
+
if (groups !== undefined) {
|
|
210
|
+
let quoted = groups.map(x => `"` + x + `"`).join(", ");
|
|
211
|
+
cognito = `@aws_cognito_user_pools(cognito_groups: [` + quoted + `])`;
|
|
212
|
+
} else {
|
|
213
|
+
cognito = `@aws_cognito_user_pools`;
|
|
214
|
+
}
|
|
215
|
+
return cognito + ` @aws_iam`;
|
|
216
|
+
}
|
|
217
|
+
|
|
207
218
|
function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
208
219
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
|
|
220
|
+
let iamFields = {};
|
|
209
221
|
let mutationAuthMap = {};
|
|
210
222
|
mutationEntries.forEach(entry => {
|
|
223
|
+
if (Stdlib_Option.getOr(entry.iamCallable, false)) {
|
|
224
|
+
entry.fieldNames.forEach(fieldName => {
|
|
225
|
+
iamFields[fieldName] = true;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
211
228
|
let match = entry.authorization;
|
|
212
229
|
if (match !== undefined) {
|
|
213
230
|
let group = match.group;
|
|
@@ -232,6 +249,10 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
232
249
|
});
|
|
233
250
|
let queryAuthMap = {};
|
|
234
251
|
queryEntries.forEach(entry => {
|
|
252
|
+
if (Stdlib_Option.getOr(entry.iamCallable, false)) {
|
|
253
|
+
iamFields[entry.singleFieldName] = true;
|
|
254
|
+
iamFields[entry.listFieldName] = true;
|
|
255
|
+
}
|
|
235
256
|
let match = entry.authorization;
|
|
236
257
|
if (match !== undefined) {
|
|
237
258
|
let group = match.group;
|
|
@@ -255,7 +276,9 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
255
276
|
let augmentedMutations = parts.mutations.map(field => {
|
|
256
277
|
let fieldName = GraphQL_Stitcher$ReventlessCore.extractLeadingName(field);
|
|
257
278
|
let groups = mutationAuthMap[fieldName];
|
|
258
|
-
if (
|
|
279
|
+
if (Stdlib_Option.getOr(iamFields[fieldName], false)) {
|
|
280
|
+
return field + `\n ` + _formatDualAuthDirective(groups);
|
|
281
|
+
} else if (groups !== undefined) {
|
|
259
282
|
return field + `\n ` + _formatGroupsDirective(groups);
|
|
260
283
|
} else {
|
|
261
284
|
return field;
|
|
@@ -264,7 +287,9 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
264
287
|
let augmentedQueries = parts.queries.map(field => {
|
|
265
288
|
let fieldName = GraphQL_Stitcher$ReventlessCore.extractLeadingName(field);
|
|
266
289
|
let groups = queryAuthMap[fieldName];
|
|
267
|
-
if (
|
|
290
|
+
if (Stdlib_Option.getOr(iamFields[fieldName], false)) {
|
|
291
|
+
return field + ` ` + _formatDualAuthDirective(groups);
|
|
292
|
+
} else if (groups !== undefined) {
|
|
268
293
|
return field + ` ` + _formatGroupsDirective(groups);
|
|
269
294
|
} else {
|
|
270
295
|
return field;
|
|
@@ -294,10 +319,23 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
294
319
|
};
|
|
295
320
|
}
|
|
296
321
|
|
|
297
|
-
function injectAwsAuthAll(fragment, group) {
|
|
322
|
+
function injectAwsAuthAll(fragment, group, iamFieldNamesOpt) {
|
|
323
|
+
let iamFieldNames = iamFieldNamesOpt !== undefined ? iamFieldNamesOpt : [];
|
|
298
324
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
|
|
299
|
-
let augmentedMutations = parts.mutations.map(field =>
|
|
300
|
-
|
|
325
|
+
let augmentedMutations = parts.mutations.map(field => {
|
|
326
|
+
if (iamFieldNames.includes(GraphQL_Stitcher$ReventlessCore.extractLeadingName(field))) {
|
|
327
|
+
return field + `\n ` + _formatDualAuthDirective([group]);
|
|
328
|
+
} else {
|
|
329
|
+
return field + `\n @aws_auth(cognito_groups: ["` + group + `"])`;
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
let augmentedQueries = parts.queries.map(field => {
|
|
333
|
+
if (iamFieldNames.includes(GraphQL_Stitcher$ReventlessCore.extractLeadingName(field))) {
|
|
334
|
+
return field + ` ` + _formatDualAuthDirective([group]);
|
|
335
|
+
} else {
|
|
336
|
+
return field + ` @aws_auth(cognito_groups: ["` + group + `"])`;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
301
339
|
let augmentedSubscriptions = parts.subscriptions.map(field => field + `\n @aws_auth(cognito_groups: ["` + group + `"])`);
|
|
302
340
|
let encoded = JSON.stringify(Object.fromEntries([
|
|
303
341
|
[
|
|
@@ -359,7 +397,7 @@ function generateFragment(mutationEntries, queryEntries) {
|
|
|
359
397
|
}
|
|
360
398
|
|
|
361
399
|
function updateSchema(api, baseFragment, pluginFragments) {
|
|
362
|
-
let augmentedBaseFragment = injectAwsAuthAll(baseFragment, "Admin");
|
|
400
|
+
let augmentedBaseFragment = injectAwsAuthAll(baseFragment, "Admin", undefined);
|
|
363
401
|
let sdl = GraphQL_Stitcher$ReventlessCore.stitch(augmentedBaseFragment, pluginFragments);
|
|
364
402
|
let resultPromise = {
|
|
365
403
|
contents: Promise.resolve()
|
|
@@ -387,6 +425,7 @@ export {
|
|
|
387
425
|
getClient,
|
|
388
426
|
_permissionToCognitoGroups,
|
|
389
427
|
_formatGroupsDirective,
|
|
428
|
+
_formatDualAuthDirective,
|
|
390
429
|
injectAwsAuth,
|
|
391
430
|
injectAwsAuthAll,
|
|
392
431
|
makeApiResource,
|