@reventlessdev/reventless-aws 3.0.0-alpha.210 → 3.0.0-alpha.212
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 +20 -0
- package/package.json +6 -6
- package/src/Platform.res +116 -849
- package/src/Platform.res.mjs +75 -710
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +16 -3
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +11 -1
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res +4 -47
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +1 -30
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +0 -11
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +0 -5
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +6 -7
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +4 -1
- package/src/components/Api/AppSync_Adapter.res +32 -105
- package/src/components/Api/AppSync_Adapter.res.mjs +12 -53
- package/src/components/Api/AppSync_MergedApi.res +1 -1
- package/src/components/Api/AppSync_SdlDecorate.res +6 -65
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +1 -31
- package/src/components/Plugin.res.mjs +1 -2
- package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res +40 -35
- package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res.mjs +14 -15
- package/src/plugin/runtime/PluginRuntime_Builder.res +58 -69
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +31 -29
- package/tests/AppSync_AdapterTest.res +23 -15
- package/tests/AppSync_AdapterTest.res.mjs +18 -16
- package/tests/AppSync_SdlDecorateTest.res +9 -89
- package/tests/AppSync_SdlDecorateTest.res.mjs +7 -72
- package/tests/MCP_LambdaTest.res +4 -6
- package/tests/MCP_LambdaTest.res.mjs +2 -2
- package/src/adapter/Api/ApiFragmentDeregistration.res +0 -138
- package/src/adapter/Api/ApiFragmentDeregistration.res.mjs +0 -108
- package/src/adapter/Api/ApiSchemaPush.res +0 -79
- package/src/adapter/Api/ApiSchemaPush.res.mjs +0 -64
- package/src/adapter/Api/Platform_ApiFragments_Lambda.res +0 -222
- package/src/adapter/Api/Platform_ApiFragments_Lambda.res.mjs +0 -202
- package/src/adapter/QueryDb/NodeResolver_AppSync.res +0 -71
- package/src/adapter/QueryDb/NodeResolver_AppSync.res.mjs +0 -44
- package/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs +0 -204
- package/tests/ApiSchemaPushTest.res +0 -32
- package/tests/ApiSchemaPushTest.res.mjs +0 -20
|
@@ -119,19 +119,14 @@ let injectAwsAuthAll = (
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// Shared traversal types every callable surface reaches — `PageInfo` (relay
|
|
122
|
-
// connections, injected by the stitcher)
|
|
123
|
-
// returns
|
|
124
|
-
// (
|
|
125
|
-
// waiter polls via SigV4 — without the type-level `@aws_iam` the SigV4 caller reaches the
|
|
126
|
-
// query field but is "Not Authorized to access <field> on type Platform_ApiFragmentEntry").
|
|
127
|
-
// Stamped once on the ASSEMBLED SDL: per-fragment stamping would race the stitcher's
|
|
128
|
-
// first-wins dedupe against unstamped sibling copies.
|
|
122
|
+
// connections, injected by the stitcher) and the `CommandResult` members
|
|
123
|
+
// (mutation returns). Stamped once on the ASSEMBLED SDL so an IAM system
|
|
124
|
+
// caller (`@@reventless.systemCallable` fields) can traverse them.
|
|
129
125
|
let sharedIamTypeNames = [
|
|
130
126
|
"PageInfo",
|
|
131
127
|
"CommandAccepted",
|
|
132
128
|
"CommandRejected",
|
|
133
129
|
"CommandPending",
|
|
134
|
-
"Platform_ApiFragmentEntry",
|
|
135
130
|
]
|
|
136
131
|
|
|
137
132
|
let stampSharedIamTypes = (sdl: string): string =>
|
|
@@ -186,60 +181,6 @@ let stampCanonicalTypes = (sdl: string): string =>
|
|
|
186
181
|
})
|
|
187
182
|
->Array.join("\n")
|
|
188
183
|
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
// API, as the bare string "Domain" / "Platform"), it produces one fully
|
|
193
|
-
// AppSync-decorated SDL push plan per API. It reuses the core planner for the
|
|
194
|
-
// neutral base-selection + stitch, then applies the AppSync dialect exactly as the
|
|
195
|
-
// deploy path's `stitchWithAwsDirectives`:
|
|
196
|
-
// - the admin base is auth-decorated (group "Admin" + dual-auth on the system-
|
|
197
|
-
// caller field names) BEFORE stitching, so the Platform/unified base carries
|
|
198
|
-
// @aws_auth/@aws_iam (the empty Domain-split base needs none);
|
|
199
|
-
// - @aws_subscribe is injected from the neutral subscriptionSources metadata;
|
|
200
|
-
// - the shared traversal types are stamped once on the assembled SDL.
|
|
201
|
-
// Plugin fragments already carry their own per-field @aws_auth (baked at
|
|
202
|
-
// generateFragment time), so they stitch in as-is. `api` comes back as the bare
|
|
203
|
-
// string "PlatformApi" / "DomainApi" for the mjs to map onto the AppSync ids.
|
|
204
|
-
type targetedFragmentInput = {
|
|
205
|
-
encoded: string,
|
|
206
|
-
protocol: string,
|
|
207
|
-
// "Domain" | "Platform" — the bare-string runtime form of Reventless.Plugin.apiTarget.
|
|
208
|
-
target: string,
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
type awsPushPlan = {
|
|
212
|
-
// "PlatformApi" | "DomainApi" — the bare-string runtime form of GraphQL_PushPlanner.planTarget.
|
|
213
|
-
api: string,
|
|
214
|
-
sdl: string,
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
let planAwsPushes = (
|
|
218
|
-
~rawAdminBase: Reventless.Plugin.apiSchemaFragment,
|
|
219
|
-
~iamFieldNames: array<string>,
|
|
220
|
-
~fragments: array<targetedFragmentInput>,
|
|
221
|
-
~splitApi: bool,
|
|
222
|
-
): array<awsPushPlan> => {
|
|
223
|
-
let authBase = injectAwsAuthAll(rawAdminBase, ~group="Admin", ~iamFieldNames)
|
|
224
|
-
let targeted = fragments->Array.map((f): ReventlessCore.GraphQL_PushPlanner.targetedFragment => {
|
|
225
|
-
fragment: {encoded: f.encoded, protocol: f.protocol},
|
|
226
|
-
target: switch f.target {
|
|
227
|
-
| "Platform" => Platform
|
|
228
|
-
| _ => Domain
|
|
229
|
-
},
|
|
230
|
-
})
|
|
231
|
-
let allFrags = targeted->Array.map(t => t.fragment)
|
|
232
|
-
let sources = ReventlessCore.GraphQL_Stitcher.collectSubscriptionSources(
|
|
233
|
-
~baseFragment=rawAdminBase,
|
|
234
|
-
~pluginFragments=allFrags,
|
|
235
|
-
)
|
|
236
|
-
ReventlessCore.GraphQL_PushPlanner.planPushes(~adminBase=authBase, ~fragments=targeted, ~splitApi)
|
|
237
|
-
->Array.map(plan => {
|
|
238
|
-
let sdl = plan.sdl->injectAwsSubscribe(~sources)->stampSharedIamTypes
|
|
239
|
-
let api = switch plan.api {
|
|
240
|
-
| PlatformApi => "PlatformApi"
|
|
241
|
-
| DomainApi => "DomainApi"
|
|
242
|
-
}
|
|
243
|
-
{api, sdl}
|
|
244
|
-
})
|
|
245
|
-
}
|
|
184
|
+
// (The reactive push planner — planAwsPushes, the AWS counterpart of core
|
|
185
|
+
// GraphQL_PushPlanner — was retired with the merged-API cutover: source APIs
|
|
186
|
+
// are single writers, AWS composes the merged endpoint, nothing re-stitches.)
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
4
|
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
5
5
|
import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
6
|
-
import * as GraphQL_PushPlanner$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_PushPlanner.res.mjs";
|
|
7
6
|
|
|
8
7
|
function injectAwsSubscribe(sdl, sources) {
|
|
9
8
|
if (sources.length === 0) {
|
|
@@ -80,8 +79,7 @@ let sharedIamTypeNames = [
|
|
|
80
79
|
"PageInfo",
|
|
81
80
|
"CommandAccepted",
|
|
82
81
|
"CommandRejected",
|
|
83
|
-
"CommandPending"
|
|
84
|
-
"Platform_ApiFragmentEntry"
|
|
82
|
+
"CommandPending"
|
|
85
83
|
];
|
|
86
84
|
|
|
87
85
|
function stampSharedIamTypes(sdl) {
|
|
@@ -125,33 +123,6 @@ function stampCanonicalTypes(sdl) {
|
|
|
125
123
|
}).join("\n");
|
|
126
124
|
}
|
|
127
125
|
|
|
128
|
-
function planAwsPushes(rawAdminBase, iamFieldNames, fragments, splitApi) {
|
|
129
|
-
let authBase = injectAwsAuthAll(rawAdminBase, "Admin", iamFieldNames);
|
|
130
|
-
let targeted = fragments.map(f => {
|
|
131
|
-
let match = f.target;
|
|
132
|
-
let tmp = match === "Platform" ? "Platform" : "Domain";
|
|
133
|
-
return {
|
|
134
|
-
fragment: {
|
|
135
|
-
encoded: f.encoded,
|
|
136
|
-
protocol: f.protocol
|
|
137
|
-
},
|
|
138
|
-
target: tmp
|
|
139
|
-
};
|
|
140
|
-
});
|
|
141
|
-
let allFrags = targeted.map(t => t.fragment);
|
|
142
|
-
let sources = GraphQL_Stitcher$ReventlessCore.collectSubscriptionSources(rawAdminBase, allFrags);
|
|
143
|
-
return GraphQL_PushPlanner$ReventlessCore.planPushes(authBase, targeted, splitApi).map(plan => {
|
|
144
|
-
let sdl = stampSharedIamTypes(injectAwsSubscribe(plan.sdl, sources));
|
|
145
|
-
let match = plan.api;
|
|
146
|
-
let api;
|
|
147
|
-
api = match === "DomainApi" ? "DomainApi" : "PlatformApi";
|
|
148
|
-
return {
|
|
149
|
-
api: api,
|
|
150
|
-
sdl: sdl
|
|
151
|
-
};
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
126
|
export {
|
|
156
127
|
injectAwsSubscribe,
|
|
157
128
|
formatDualAuthDirective,
|
|
@@ -160,6 +131,5 @@ export {
|
|
|
160
131
|
stampSharedIamTypes,
|
|
161
132
|
canonicalTypeNames,
|
|
162
133
|
stampCanonicalTypes,
|
|
163
|
-
planAwsPushes,
|
|
164
134
|
}
|
|
165
135
|
/* GraphQL_Stitcher-ReventlessCore Not a pure module */
|
|
@@ -28,8 +28,7 @@ function Make(HooksConfig) {
|
|
|
28
28
|
hooks: HooksConfig.hooks
|
|
29
29
|
})({})({
|
|
30
30
|
makeApiResource: AppSync_Adapter$ReventlessAws.makeApiResource,
|
|
31
|
-
generateFragment: AppSync_Adapter$ReventlessAws.generateFragment
|
|
32
|
-
updateSchema: AppSync_Adapter$ReventlessAws.updateSchema
|
|
31
|
+
generateFragment: AppSync_Adapter$ReventlessAws.generateFragment
|
|
33
32
|
})({
|
|
34
33
|
make: RuntimeEnvironment_Lambda$ReventlessAws.make,
|
|
35
34
|
groupBySource: RuntimeEnvironment_Lambda$ReventlessAws.groupBySource,
|
|
@@ -25,16 +25,48 @@ let make: ReventlessCore.Heartbeat_Adapter.runnerMaker<runtimeParts> = (
|
|
|
25
25
|
let lambdaRole = runtime.parts.lambdaRole
|
|
26
26
|
let coreSqsQueue = remoteChannel.resources->Util_SQS.findResolvedResource
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
// Grant sqs:SendMessage on the Core Plugin ExtensionPoint command topic at
|
|
29
|
+
// TOP LEVEL. Previously this RolePolicy was created inside the
|
|
30
|
+
// `Pulumi.Output.apply` below — and a resource created inside an apply
|
|
31
|
+
// callback does not reliably register with the engine, which intermittently
|
|
32
|
+
// left the heartbeat Lambda without this grant (IAM AccessDenied:
|
|
33
|
+
// `sqs:sendmessage on CorePluginExtPointCmdTopic`). `coreSqsQueue.urn` is an
|
|
34
|
+
// already-resolved ARN string from the remote channel and `lambdaRole.id` an
|
|
35
|
+
// Output, so neither needs an apply. Mirrors the EC `pta` grant fix — see
|
|
36
|
+
// docs/analysis/ec-publish-to-aggregates-grant-broken.md.
|
|
37
|
+
let _attachHeartbeatLambdaPolicy = {
|
|
38
|
+
open PulumiAws.PolicyDocument
|
|
39
|
+
let heartbeatLambdaSendMessagePolicyDocument = PulumiAws.PolicyDocument.make(
|
|
40
|
+
~statements=[
|
|
41
|
+
{
|
|
42
|
+
sid: "AllowLambdaToSendSQS",
|
|
43
|
+
effect: Allow,
|
|
44
|
+
actions: Action("sqs:SendMessage"),
|
|
45
|
+
resources: Resource(coreSqsQueue.urn),
|
|
46
|
+
},
|
|
47
|
+
],
|
|
33
48
|
)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
PulumiAws.IAM.RolePolicy.make(
|
|
50
|
+
~name=name ++ "RolePolicy",
|
|
51
|
+
~args={
|
|
52
|
+
policy: PulumiAws.PolicyDocument.mergePolicyDocuments(
|
|
53
|
+
name ++ "Policy",
|
|
54
|
+
[
|
|
55
|
+
PulumiAws.Lambda.defaultLoggingPolicyDocument,
|
|
56
|
+
heartbeatLambdaSendMessagePolicyDocument,
|
|
57
|
+
],
|
|
58
|
+
)->Pulumi.Output.asInput,
|
|
59
|
+
role: lambdaRole.id->Pulumi.Output.asInput,
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
}
|
|
37
63
|
|
|
64
|
+
// The Lambda permission and CloudWatch event target genuinely need the
|
|
65
|
+
// Lambda's resolved arn/name, so they stay inside an apply.
|
|
66
|
+
let _permissionAndEventTarget =
|
|
67
|
+
(lambda->Pulumi.Output.flatMap(lambda => lambda.arn), lambda->Pulumi.Output.flatMap(lambda => lambda.name))
|
|
68
|
+
->Pulumi.Output.all2
|
|
69
|
+
->Pulumi.Output.apply(((lambdaArn, lambdaName)) => {
|
|
38
70
|
let _addHeartbeatLambdaPermission = PulumiAws.Lambda.Permission.make(
|
|
39
71
|
~name,
|
|
40
72
|
~args={
|
|
@@ -45,33 +77,6 @@ let make: ReventlessCore.Heartbeat_Adapter.runnerMaker<runtimeParts> = (
|
|
|
45
77
|
~opts,
|
|
46
78
|
)
|
|
47
79
|
|
|
48
|
-
let heartbeatLambdaSendMessagePolicyDocument = {
|
|
49
|
-
PulumiAws.PolicyDocument.make(
|
|
50
|
-
~statements=[
|
|
51
|
-
{
|
|
52
|
-
sid: "AllowLambdaToSendSQS",
|
|
53
|
-
effect: Allow,
|
|
54
|
-
actions: Action("sqs:SendMessage"),
|
|
55
|
-
resources: Resource(coreSqsQueue.urn),
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
let _attachHeartbeatLambdaPolicy = PulumiAws.IAM.RolePolicy.make(
|
|
62
|
-
~name=name ++ "RolePolicy",
|
|
63
|
-
~args={
|
|
64
|
-
policy: PulumiAws.PolicyDocument.mergePolicyDocuments(
|
|
65
|
-
name ++ "Policy",
|
|
66
|
-
[
|
|
67
|
-
PulumiAws.Lambda.defaultLoggingPolicyDocument,
|
|
68
|
-
heartbeatLambdaSendMessagePolicyDocument,
|
|
69
|
-
],
|
|
70
|
-
)->Pulumi.Output.asInput,
|
|
71
|
-
role: heartbeatRoleId->Pulumi.Input.make,
|
|
72
|
-
},
|
|
73
|
-
)
|
|
74
|
-
|
|
75
80
|
let _cloudwatchEventTarget = {
|
|
76
81
|
open PulumiAws.Cloudwatch
|
|
77
82
|
EventTarget.make(
|
|
@@ -27,29 +27,28 @@ function make(name, remoteChannel, timeout, runtime, opts) {
|
|
|
27
27
|
let lambda = runtime.parts.lambda;
|
|
28
28
|
let lambdaRole = runtime.parts.lambdaRole;
|
|
29
29
|
let coreSqsQueue = Util_SQS$ReventlessAws.findResolvedResource(remoteChannel.resources);
|
|
30
|
+
let heartbeatLambdaSendMessagePolicyDocument = PolicyDocument$PulumiAws.make(undefined, undefined, [{
|
|
31
|
+
Sid: "AllowLambdaToSendSQS",
|
|
32
|
+
Effect: "Allow",
|
|
33
|
+
Action: "sqs:SendMessage",
|
|
34
|
+
Resource: coreSqsQueue.urn
|
|
35
|
+
}]);
|
|
36
|
+
new (Aws.iam.RolePolicy)(name + "RolePolicy", {
|
|
37
|
+
policy: PolicyDocument$PulumiAws.mergePolicyDocuments(name + "Policy", [
|
|
38
|
+
Lambda$PulumiAws.defaultLoggingPolicyDocument,
|
|
39
|
+
heartbeatLambdaSendMessagePolicyDocument
|
|
40
|
+
]),
|
|
41
|
+
role: lambdaRole.id
|
|
42
|
+
});
|
|
30
43
|
Pulumi.all([
|
|
31
44
|
Output$Pulumi.flatMap(lambda, lambda => lambda.arn),
|
|
32
|
-
Output$Pulumi.flatMap(lambda, lambda => lambda.name)
|
|
33
|
-
lambdaRole.id
|
|
45
|
+
Output$Pulumi.flatMap(lambda, lambda => lambda.name)
|
|
34
46
|
]).apply(param => {
|
|
35
47
|
new (Aws.lambda.Permission)(name, {
|
|
36
48
|
action: "lambda:InvokeFunction",
|
|
37
49
|
function: param[1],
|
|
38
50
|
principal: AWS$ReventlessAws.CloudwatchEventRule.principal
|
|
39
51
|
}, opts$1);
|
|
40
|
-
let heartbeatLambdaSendMessagePolicyDocument = PolicyDocument$PulumiAws.make(undefined, undefined, [{
|
|
41
|
-
Sid: "AllowLambdaToSendSQS",
|
|
42
|
-
Effect: "Allow",
|
|
43
|
-
Action: "sqs:SendMessage",
|
|
44
|
-
Resource: coreSqsQueue.urn
|
|
45
|
-
}]);
|
|
46
|
-
new (Aws.iam.RolePolicy)(name + "RolePolicy", {
|
|
47
|
-
policy: PolicyDocument$PulumiAws.mergePolicyDocuments(name + "Policy", [
|
|
48
|
-
Lambda$PulumiAws.defaultLoggingPolicyDocument,
|
|
49
|
-
heartbeatLambdaSendMessagePolicyDocument
|
|
50
|
-
]),
|
|
51
|
-
role: param[2]
|
|
52
|
-
});
|
|
53
52
|
new (Aws.cloudwatch.EventTarget)(name, {
|
|
54
53
|
rule: Cloudwatch_EventTarget$PulumiAws.Rule.ofEventRule(cloudwatchEventRule),
|
|
55
54
|
arn: param[0]
|
|
@@ -622,79 +622,68 @@ module Make = (
|
|
|
622
622
|
aggregateNameSet->Dict.set(aggName, ())
|
|
623
623
|
)
|
|
624
624
|
)
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
None
|
|
625
|
+
// One RolePolicy per extension target, each derived through a SINGLE
|
|
626
|
+
// `.apply` on that target's queue-URL Output. `Pulumi.Output.all` is
|
|
627
|
+
// deliberately NOT used here: the DCB slice's URL is an `.apply`-lifted
|
|
628
|
+
// Output (Dcb_Builder reads the queue id via `operations.apply(_ =>
|
|
629
|
+
// resources[0].id)`), and `Pulumi.Output.all` mis-resolves such lifted
|
|
630
|
+
// Outputs to the `{BS_PRIVATE_NESTED_SOME_NONE: 0}` sentinel (both at
|
|
631
|
+
// preview and up) while a direct `.apply` on the same Output resolves the
|
|
632
|
+
// real URL — verified by deploying both variants. RolePolicy creation
|
|
633
|
+
// happens at TOP LEVEL: calling `RolePolicy.make` inside a
|
|
634
|
+
// `Pulumi.Output.apply` callback does not reliably register the resource
|
|
635
|
+
// (the previous inside-apply variant existed on no collector role in the
|
|
636
|
+
// account). Dict values are heterogeneous at runtime — plain resolved URL
|
|
637
|
+
// strings for aggregate targets, Outputs for DCB-slice targets; anything
|
|
638
|
+
// else is skipped. Inside the apply a non-string URL falls back to a
|
|
639
|
+
// benign placeholder ARN so the policy document stays valid (IAM rejects
|
|
640
|
+
// empty Resource entries with MalformedPolicyDocument).
|
|
641
|
+
// See docs/analysis/ec-publish-to-aggregates-grant-broken.md.
|
|
642
|
+
aggregateNameSet
|
|
643
|
+
->Dict.keysToArray
|
|
644
|
+
->Array.forEach(aggName => {
|
|
645
|
+
let urlOutput = switch context.publishToAggregates->Dict.get(aggName) {
|
|
646
|
+
| Some(o) if Pulumi.Output.isOutput(o) => Some(o)
|
|
647
|
+
| Some(o) if typeof(o) === #string => Some(Pulumi.Output.make((o->Obj.magic: string)))
|
|
648
|
+
| _ => None
|
|
649
|
+
}
|
|
650
|
+
switch urlOutput {
|
|
651
|
+
| Some(urlOutput) =>
|
|
652
|
+
let policyJson = urlOutput->Pulumi.Output.apply(url => {
|
|
653
|
+
let arn = if typeof(url) === #string {
|
|
654
|
+
// queue URL → ARN: https://sqs.<region>.amazonaws.com/<acct>/<name>
|
|
655
|
+
// → arn:aws:sqs:<region>:<acct>:<name>
|
|
656
|
+
switch url->String.split("/") {
|
|
657
|
+
| [_, _, host, acct, name] =>
|
|
658
|
+
let region = host->String.split(".")->Array.get(1)->Option.getOr("eu-west-1")
|
|
659
|
+
`arn:aws:sqs:${region}:${acct}:${name}`
|
|
660
|
+
| _ => url
|
|
661
|
+
}
|
|
663
662
|
} else {
|
|
664
|
-
|
|
665
|
-
PulumiAws.PolicyDocument.make(
|
|
666
|
-
~id=`${name}PublishToAggregatesPolicy`,
|
|
667
|
-
~statements=[
|
|
668
|
-
{
|
|
669
|
-
sid: "AllowEcPublishToAggregateCmdTopics",
|
|
670
|
-
effect: Allow,
|
|
671
|
-
actions: Action("sqs:SendMessage"),
|
|
672
|
-
resources: Resources(queueArns),
|
|
673
|
-
},
|
|
674
|
-
],
|
|
675
|
-
)->PulumiAws.PolicyDocument.toJsonString,
|
|
676
|
-
)
|
|
663
|
+
"arn:aws:sqs:eu-west-1:000000000000:reventless-unresolved-cmd-topic"
|
|
677
664
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
| Some(policyJson) =>
|
|
687
|
-
let _ = PulumiAws.IAM.RolePolicy.make(
|
|
688
|
-
~name=`${name}-publishToAggregates`,
|
|
689
|
-
~args={
|
|
690
|
-
policy: policyJson->Pulumi.Input.make,
|
|
691
|
-
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
665
|
+
PulumiAws.PolicyDocument.make(
|
|
666
|
+
~id=`${name}Pta${aggName}Policy`,
|
|
667
|
+
~statements=[
|
|
668
|
+
{
|
|
669
|
+
sid: "AllowEcPublishToAggregateCmdTopic",
|
|
670
|
+
effect: Allow,
|
|
671
|
+
actions: Action("sqs:SendMessage"),
|
|
672
|
+
resources: Resource(arn),
|
|
692
673
|
},
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
674
|
+
],
|
|
675
|
+
)->PulumiAws.PolicyDocument.toJsonString
|
|
676
|
+
})
|
|
677
|
+
let _ = PulumiAws.IAM.RolePolicy.make(
|
|
678
|
+
~name=`${name}-pta-${aggName}`,
|
|
679
|
+
~args={
|
|
680
|
+
policy: policyJson->Pulumi.Output.asInput,
|
|
681
|
+
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
682
|
+
},
|
|
696
683
|
)
|
|
697
|
-
|
|
684
|
+
| None => ()
|
|
685
|
+
}
|
|
686
|
+
})
|
|
698
687
|
}
|
|
699
688
|
|
|
700
689
|
let forPluginHeartbeat: ReventlessCore.Runtime.forComponent<
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Aws from "@pulumi/aws";
|
|
4
|
-
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
5
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
6
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
@@ -366,41 +365,44 @@ function Make(EventCollectorChannel) {
|
|
|
366
365
|
aggregateNameSet[aggName] = undefined;
|
|
367
366
|
});
|
|
368
367
|
});
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
let
|
|
372
|
-
|
|
368
|
+
Object.keys(aggregateNameSet).forEach(aggName => {
|
|
369
|
+
let o = context.publishToAggregates[aggName];
|
|
370
|
+
let urlOutput = o !== undefined ? (
|
|
371
|
+
Pulumi.Output.isInstance(o) ? o : (
|
|
372
|
+
typeof o === "string" ? Pulumi.output(o) : undefined
|
|
373
|
+
)
|
|
374
|
+
) : undefined;
|
|
375
|
+
if (urlOutput === undefined) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
let policyJson = urlOutput.apply(url => {
|
|
379
|
+
let arn;
|
|
380
|
+
if (typeof url === "string") {
|
|
373
381
|
let match = url.split("/");
|
|
374
382
|
if (match.length !== 5) {
|
|
375
|
-
|
|
383
|
+
arn = url;
|
|
384
|
+
} else {
|
|
385
|
+
let host = match[2];
|
|
386
|
+
let acct = match[3];
|
|
387
|
+
let name$1 = match[4];
|
|
388
|
+
let region = Stdlib_Option.getOr(host.split(".")[1], "eu-west-1");
|
|
389
|
+
arn = `arn:aws:sqs:` + region + `:` + acct + `:` + name$1;
|
|
376
390
|
}
|
|
377
|
-
let host = match[2];
|
|
378
|
-
let acct = match[3];
|
|
379
|
-
let name = match[4];
|
|
380
|
-
let region = Stdlib_Option.getOr(host.split(".")[1], "eu-west-1");
|
|
381
|
-
return `arn:aws:sqs:` + region + `:` + acct + `:` + name;
|
|
382
|
-
});
|
|
383
|
-
if (queueArns.length === 0) {
|
|
384
|
-
return;
|
|
385
391
|
} else {
|
|
386
|
-
|
|
387
|
-
Sid: "AllowEcPublishToAggregateCmdTopics",
|
|
388
|
-
Effect: "Allow",
|
|
389
|
-
Action: "sqs:SendMessage",
|
|
390
|
-
Resource: queueArns
|
|
391
|
-
}]));
|
|
392
|
+
arn = "arn:aws:sqs:eu-west-1:000000000000:reventless-unresolved-cmd-topic";
|
|
392
393
|
}
|
|
394
|
+
return PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `Pta` + aggName + `Policy`, [{
|
|
395
|
+
Sid: "AllowEcPublishToAggregateCmdTopic",
|
|
396
|
+
Effect: "Allow",
|
|
397
|
+
Action: "sqs:SendMessage",
|
|
398
|
+
Resource: arn
|
|
399
|
+
}]));
|
|
393
400
|
});
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
policy: policyOpt,
|
|
398
|
-
role: runtime.parts.lambdaRole.id
|
|
399
|
-
});
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
401
|
+
new (Aws.iam.RolePolicy)(name + `-pta-` + aggName, {
|
|
402
|
+
policy: policyJson,
|
|
403
|
+
role: runtime.parts.lambdaRole.id
|
|
402
404
|
});
|
|
403
|
-
}
|
|
405
|
+
});
|
|
404
406
|
};
|
|
405
407
|
let forPluginHeartbeat = (param, connect, memorySizeOpt, timeoutOpt, heartbeat) => {
|
|
406
408
|
let memorySize = memorySizeOpt !== undefined ? memorySizeOpt : 1024;
|
|
@@ -663,13 +663,15 @@ describe("Split mode — empty base fragment", () => {
|
|
|
663
663
|
|
|
664
664
|
describe("Merged mode — canonical source documents", () => {
|
|
665
665
|
let assembleCanonicalSourceSdl = (~baseFragment) =>
|
|
666
|
-
AppSync_Adapter.
|
|
666
|
+
AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment=baseFragment)
|
|
667
667
|
->AppSync_SdlDecorate.stampCanonicalTypes
|
|
668
668
|
|
|
669
|
-
|
|
669
|
+
// Mirrors Platform.res's domainBaseFragment: no component fields, one
|
|
670
|
+
// Platform_ping so the Query type is non-empty.
|
|
671
|
+
let domainBaseFragment = ReventlessCore.GraphQL_Stitcher.encode({
|
|
670
672
|
types: [],
|
|
671
673
|
mutations: [],
|
|
672
|
-
queries: [],
|
|
674
|
+
queries: [" Platform_ping: String"],
|
|
673
675
|
subscriptions: [],
|
|
674
676
|
subscriptionSources: [],
|
|
675
677
|
})
|
|
@@ -678,14 +680,17 @@ describe("Merged mode — canonical source documents", () => {
|
|
|
678
680
|
~baseFragment=AppSync_Adapter.injectAwsAuthAll(
|
|
679
681
|
ReventlessCore.AdminApi.baseFragment(~cloner=false),
|
|
680
682
|
~group="Admin",
|
|
681
|
-
~iamFieldNames=ReventlessCore.AdminApi.systemCallerFieldNames,
|
|
682
683
|
),
|
|
683
684
|
)
|
|
684
685
|
|
|
685
|
-
let
|
|
686
|
+
let domainBaseSourceSdl = assembleCanonicalSourceSdl(~baseFragment=domainBaseFragment)
|
|
686
687
|
|
|
687
|
-
testSync("
|
|
688
|
-
expect(adminSourceSdl)->toContain("node(id: ID!): Node")
|
|
688
|
+
testSync("no AWS source document carries the global node query (dropped — see plan)", () => {
|
|
689
|
+
expect(adminSourceSdl)->not_->toContain("node(id: ID!): Node")
|
|
690
|
+
expect(domainBaseSourceSdl)->not_->toContain("node(id: ID!): Node")
|
|
691
|
+
// The Node interface and global IDs stay — they are what makes a future
|
|
692
|
+
// node() possible without a schema migration.
|
|
693
|
+
expect(adminSourceSdl)->toContain("interface Node")
|
|
689
694
|
})
|
|
690
695
|
|
|
691
696
|
testSync("admin source document stamps @canonical on the shared traversal types", () => {
|
|
@@ -697,19 +702,22 @@ describe("Merged mode — canonical source documents", () => {
|
|
|
697
702
|
)
|
|
698
703
|
})
|
|
699
704
|
|
|
700
|
-
testSync("admin source document keeps admin fields and
|
|
705
|
+
testSync("admin source document keeps admin fields and shared-type IAM stamps", () => {
|
|
701
706
|
expect(adminSourceSdl)->toContain("Platform_Plugin")
|
|
702
|
-
|
|
707
|
+
// Type-level dual-auth on the shared traversal types (stampSharedIamTypes);
|
|
708
|
+
// no FIELD carries @aws_iam anymore — the SigV4 register/deregister surface
|
|
709
|
+
// died with the fragment registry.
|
|
710
|
+
expect(adminSourceSdl)->toContain("type PageInfo @aws_cognito_user_pools @aws_iam")
|
|
703
711
|
})
|
|
704
712
|
|
|
705
|
-
testSync("
|
|
706
|
-
expect(
|
|
707
|
-
expect(
|
|
708
|
-
expect(
|
|
713
|
+
testSync("Domain base source document (split mode) is relay types + Platform_ping only", () => {
|
|
714
|
+
expect(domainBaseSourceSdl)->toContain("Platform_ping: String")
|
|
715
|
+
expect(domainBaseSourceSdl)->toContain("interface Node @canonical {")
|
|
716
|
+
expect(domainBaseSourceSdl)->toContain("type PageInfo")
|
|
709
717
|
// No admin or plugin fields on the split-mode Domain source.
|
|
710
|
-
expect(
|
|
718
|
+
expect(domainBaseSourceSdl)->not_->toContain("Platform_Plugin")
|
|
711
719
|
// No mutations → no CommandResult family on this source.
|
|
712
|
-
expect(
|
|
720
|
+
expect(domainBaseSourceSdl)->not_->toContain("union CommandResult")
|
|
713
721
|
})
|
|
714
722
|
|
|
715
723
|
// Phase 4: the plugin-stack subgraph document pushed to the plugin's own
|
|
@@ -607,18 +607,20 @@ globalThis.describe("Split mode — empty base fragment", () => {
|
|
|
607
607
|
});
|
|
608
608
|
|
|
609
609
|
globalThis.describe("Merged mode — canonical source documents", () => {
|
|
610
|
-
let
|
|
610
|
+
let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
|
|
611
611
|
types: [],
|
|
612
612
|
mutations: [],
|
|
613
|
-
queries: [],
|
|
613
|
+
queries: [" Platform_ping: String"],
|
|
614
614
|
subscriptions: [],
|
|
615
615
|
subscriptionSources: []
|
|
616
616
|
});
|
|
617
|
-
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin",
|
|
618
|
-
let adminSourceSdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.
|
|
619
|
-
let
|
|
620
|
-
globalThis.test("
|
|
621
|
-
globalThis.expect(adminSourceSdl).toContain("node(id: ID!): Node");
|
|
617
|
+
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(AdminApi$ReventlessCore.baseFragment(false), "Admin", undefined);
|
|
618
|
+
let adminSourceSdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(baseFragment));
|
|
619
|
+
let domainBaseSourceSdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(domainBaseFragment));
|
|
620
|
+
globalThis.test("no AWS source document carries the global node query (dropped — see plan)", () => {
|
|
621
|
+
globalThis.expect(adminSourceSdl).not.toContain("node(id: ID!): Node");
|
|
622
|
+
globalThis.expect(domainBaseSourceSdl).not.toContain("node(id: ID!): Node");
|
|
623
|
+
globalThis.expect(adminSourceSdl).toContain("interface Node");
|
|
622
624
|
});
|
|
623
625
|
globalThis.test("admin source document stamps @canonical on the shared traversal types", () => {
|
|
624
626
|
globalThis.expect(adminSourceSdl).toContain("type PageInfo @aws_cognito_user_pools @aws_iam @canonical {");
|
|
@@ -626,16 +628,16 @@ globalThis.describe("Merged mode — canonical source documents", () => {
|
|
|
626
628
|
globalThis.expect(adminSourceSdl).toContain("union CommandResult @canonical =");
|
|
627
629
|
globalThis.expect(adminSourceSdl).toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam @canonical {");
|
|
628
630
|
});
|
|
629
|
-
globalThis.test("admin source document keeps admin fields and
|
|
631
|
+
globalThis.test("admin source document keeps admin fields and shared-type IAM stamps", () => {
|
|
630
632
|
globalThis.expect(adminSourceSdl).toContain("Platform_Plugin");
|
|
631
|
-
globalThis.expect(adminSourceSdl).toContain("@aws_iam");
|
|
632
|
-
});
|
|
633
|
-
globalThis.test("
|
|
634
|
-
globalThis.expect(
|
|
635
|
-
globalThis.expect(
|
|
636
|
-
globalThis.expect(
|
|
637
|
-
globalThis.expect(
|
|
638
|
-
globalThis.expect(
|
|
633
|
+
globalThis.expect(adminSourceSdl).toContain("type PageInfo @aws_cognito_user_pools @aws_iam");
|
|
634
|
+
});
|
|
635
|
+
globalThis.test("Domain base source document (split mode) is relay types + Platform_ping only", () => {
|
|
636
|
+
globalThis.expect(domainBaseSourceSdl).toContain("Platform_ping: String");
|
|
637
|
+
globalThis.expect(domainBaseSourceSdl).toContain("interface Node @canonical {");
|
|
638
|
+
globalThis.expect(domainBaseSourceSdl).toContain("type PageInfo");
|
|
639
|
+
globalThis.expect(domainBaseSourceSdl).not.toContain("Platform_Plugin");
|
|
640
|
+
globalThis.expect(domainBaseSourceSdl).not.toContain("union CommandResult");
|
|
639
641
|
});
|
|
640
642
|
let pluginFragment = GraphQL_Stitcher$ReventlessCore.encode({
|
|
641
643
|
types: [
|