@reventlessdev/reventless-aws 3.0.0-alpha.171 → 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 +23 -0
- package/package.json +6 -5
- package/rescript.json +2 -1
- package/src/Platform.res.mjs +5 -5
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +1 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +12 -6
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +17 -5
- 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/Postgres/PgConnection.res +169 -0
- package/src/adapter/Postgres/PgConnection.res.mjs +112 -0
- package/src/adapter/Postgres/PgRuntime.res +71 -0
- package/src/adapter/Postgres/PgRuntime.res.mjs +71 -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/src/util/Util_AppSync_Caller.res +24 -4
- package/src/util/Util_AppSync_Caller.res.mjs +7 -4
- package/tests/AppSync_AdapterTest.res +140 -0
- package/tests/AppSync_AdapterTest.res.mjs +182 -6
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +7 -3
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +18 -3
- package/tests/PgChangeFeedRelay_RuntimeTest.res +44 -0
- package/tests/PgChangeFeedRelay_RuntimeTest.res.mjs +70 -0
- package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res +2 -1
- package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res.mjs +2 -2
|
@@ -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,
|
|
@@ -66,7 +66,7 @@ function forCommandTopic(param, connect, memorySizeOpt, timeoutOpt, param$1, par
|
|
|
66
66
|
});
|
|
67
67
|
envVars["HANDLER_CONFIG"] = handlerConfigJson;
|
|
68
68
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs", {}, undefined);
|
|
69
|
-
return connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, opts));
|
|
69
|
+
return connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts));
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function finish() {
|
|
@@ -300,7 +300,7 @@ function Make(EventCollectorChannel) {
|
|
|
300
300
|
});
|
|
301
301
|
let codeOutput = bundleOutput.apply(b => b.code);
|
|
302
302
|
let sourceCodeHashOutput = bundleOutput.apply(b => b.sourceCodeHash);
|
|
303
|
-
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, codeOutput, sourceCodeHashOutput, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, opts);
|
|
303
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, codeOutput, sourceCodeHashOutput, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts);
|
|
304
304
|
EventCollectorChannel.connect(name, [{
|
|
305
305
|
channel: eventCollector.channel,
|
|
306
306
|
eventTopics: eventTopics,
|
|
@@ -422,7 +422,7 @@ function Make(EventCollectorChannel) {
|
|
|
422
422
|
envVars["PLUGIN_ID"] = Pulumi.output(hbConfig.pluginId);
|
|
423
423
|
envVars["HEARTBEAT_TIMEOUT"] = Pulumi.output(hbConfig.heartbeatTimeout.toString());
|
|
424
424
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/HeartbeatEntryPoint.mjs", {}, undefined);
|
|
425
|
-
connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, opts));
|
|
425
|
+
connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts));
|
|
426
426
|
};
|
|
427
427
|
let forDcbCommandTopic = (param, connect, $staropt$star, $staropt$star$1, dcbCommandTopic) => {
|
|
428
428
|
$staropt$star !== undefined;
|
|
@@ -107,13 +107,25 @@ let rec jsonToLiteral = (value: JSON.t): string =>
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
// ~selection is the sub-selection appended after the field call. It defaults to
|
|
111
|
+
// `{ __typename }` because every command mutation dispatched through this caller
|
|
112
|
+
// returns the `CommandResult` union (CommandAccepted | CommandPending |
|
|
113
|
+
// CommandRejected); GraphQL rejects a selectionless field on any object/union
|
|
114
|
+
// type with `SubSelectionRequired`. Pass ~selection="" for a scalar-returning
|
|
115
|
+
// mutation, or a richer set (e.g. "{ __typename ... on CommandRejected { reason } }")
|
|
116
|
+
// to inspect the result.
|
|
117
|
+
let buildQuery = (
|
|
118
|
+
~mutation: string,
|
|
119
|
+
~selection: string="{ __typename }",
|
|
120
|
+
~variablesDict: dict<JSON.t>,
|
|
121
|
+
): string => {
|
|
111
122
|
let args =
|
|
112
123
|
variablesDict
|
|
113
124
|
->Dict.toArray
|
|
114
125
|
->Array.map(((k, v)) => `${k}: ${jsonToLiteral(v)}`)
|
|
115
126
|
->Array.join(", ")
|
|
116
|
-
`
|
|
127
|
+
let sel = selection == "" ? "" : ` ${selection}`
|
|
128
|
+
`mutation { ${mutation}(${args})${sel} }`
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
// ── sendMutation ────────────────────────────────────────────────────────────
|
|
@@ -121,6 +133,8 @@ let buildQuery = (~mutation: string, ~variablesDict: dict<JSON.t>): string => {
|
|
|
121
133
|
// Signs and sends a single GraphQL mutation to an AppSync endpoint via IAM.
|
|
122
134
|
// ~variables accepts any value; undefined/optional fields are omitted by
|
|
123
135
|
// JSON.stringifyAny (mirrors how JSON.stringify drops undefined object keys).
|
|
136
|
+
// ~selection defaults to `{ __typename }` so CommandResult-union mutations
|
|
137
|
+
// validate (see buildQuery); pass ~selection="" for scalar-returning mutations.
|
|
124
138
|
// Returns a promise — callers fire-and-forget with `let _ = sendMutation(...)`.
|
|
125
139
|
|
|
126
140
|
// Signs and sends a GraphQL query; returns the `data` object or None on error.
|
|
@@ -164,7 +178,13 @@ let sendQuery = async (~endpoint: string, ~region: string, ~queryString: string)
|
|
|
164
178
|
}
|
|
165
179
|
}
|
|
166
180
|
|
|
167
|
-
let sendMutation = async (
|
|
181
|
+
let sendMutation = async (
|
|
182
|
+
~endpoint: string,
|
|
183
|
+
~region: string,
|
|
184
|
+
~mutation: string,
|
|
185
|
+
~selection: string="{ __typename }",
|
|
186
|
+
~variables: 'a,
|
|
187
|
+
) => {
|
|
168
188
|
let url = parseUrl(endpoint)
|
|
169
189
|
let hostname: string = (url)["hostname"]
|
|
170
190
|
let path: string = (url)["pathname"]
|
|
@@ -180,7 +200,7 @@ let sendMutation = async (~endpoint: string, ~region: string, ~mutation: string,
|
|
|
180
200
|
| None => Dict.make()
|
|
181
201
|
}
|
|
182
202
|
|
|
183
|
-
let query = buildQuery(~mutation, ~variablesDict)
|
|
203
|
+
let query = buildQuery(~mutation, ~selection, ~variablesDict)
|
|
184
204
|
let body =
|
|
185
205
|
Dict.fromArray([("query", query->JSON.Encode.string)])
|
|
186
206
|
->JSON.Encode.object
|