@reventlessdev/reventless-aws 3.0.0-alpha.222 → 3.0.0-alpha.224
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 -6
- package/src/Platform.res +3 -3
- package/src/Platform.res.mjs +3 -3
- package/src/adapter/Adapter_Helpers.res +6 -0
- package/src/adapter/Adapter_Helpers.res.mjs +6 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res +1 -1
- package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res +65 -47
- package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs +52 -39
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res +61 -59
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res.mjs +10 -16
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +32 -30
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +20 -22
- package/src/adapter/Mcp/MCP_Lambda.res +4 -2
- package/src/adapter/Mcp/MCP_Lambda.res.mjs +3 -2
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +1 -1
- package/src/adapter/Runtime/{AdminEventCollectorEntryPoint.mjs → EventCollectorEntryPoint.mjs} +5 -5
- package/src/components/Api/AppSync_SdlDecorate.res +1 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res +1 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +2 -2
- package/src/plugin/runtime/PluginRuntime_Builder.res +9 -5
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +3 -2
- package/src/util/Util_DeadLetterQueue.res +13 -3
- package/src/util/Util_DeadLetterQueue.res.mjs +2 -2
- package/tests/AppSync_AdapterTest.res +7 -7
- package/tests/AppSync_AdapterTest.res.mjs +8 -8
- package/tests/CommandTopicQueuePolicyTest.res +65 -0
- package/tests/CommandTopicQueuePolicyTest.res.mjs +53 -0
- package/tests/MCP_LambdaTest.res +1 -1
- package/tests/MCP_LambdaTest.res.mjs +2 -2
|
@@ -114,38 +114,40 @@ let make = (
|
|
|
114
114
|
|
|
115
115
|
// SQS queue policy — allow SNS to send messages
|
|
116
116
|
let snsResource = eventTopicOutputs.resources->Array.getUnsafe(0)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
// The document is a *value* derived from resolved ARNs, so it is built in an
|
|
118
|
+
// apply. The QueuePolicy itself stays outside: passing `queue.id` as an Output
|
|
119
|
+
// is what registers the policy -> queue dependency, without which Pulumi has
|
|
120
|
+
// no ordering constraint and can delete the queue first on a replacement,
|
|
121
|
+
// leaving the policy's delete polling a queue that no longer exists.
|
|
122
|
+
let queuePolicyDocument =
|
|
123
|
+
(queue.arn, snsResource.urn)
|
|
124
|
+
->Pulumi.Output.all2
|
|
125
|
+
->Pulumi.Output.apply(((queueArn, snsUrn)) => {
|
|
121
126
|
open PolicyDocument
|
|
122
|
-
|
|
123
|
-
~
|
|
124
|
-
~
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
("aws:SourceArn", ConditionValues([snsUrn])),
|
|
138
|
-
]->Dict.fromArray,
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
],
|
|
142
|
-
)
|
|
143
|
-
->PolicyDocument.toJsonString
|
|
144
|
-
->Pulumi.Input.make,
|
|
145
|
-
},
|
|
146
|
-
~opts=Some(opts),
|
|
147
|
-
)
|
|
127
|
+
PolicyDocument.make(
|
|
128
|
+
~id=name ++ "EventLogSubQueuePolicy",
|
|
129
|
+
~statements=[
|
|
130
|
+
{
|
|
131
|
+
sid: "AllowSNSSend",
|
|
132
|
+
principal: Principals({service: PrincipalIds([AWS.SNS.principal])}),
|
|
133
|
+
effect: Allow,
|
|
134
|
+
actions: Actions(["sqs:SendMessage"]),
|
|
135
|
+
resources: Resource(queueArn),
|
|
136
|
+
conditions: {
|
|
137
|
+
arnEquals: [("aws:SourceArn", ConditionValues([snsUrn]))]->Dict.fromArray,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
)->PolicyDocument.toJsonString
|
|
148
142
|
})
|
|
143
|
+
let _queuePolicy = SQS.QueuePolicy.make(
|
|
144
|
+
~name=name ++ "EventLogSubQueuePolicy",
|
|
145
|
+
~args={
|
|
146
|
+
queueUrl: queue.id->Pulumi.Output.asInput,
|
|
147
|
+
policy: queuePolicyDocument->Pulumi.Output.asInput,
|
|
148
|
+
},
|
|
149
|
+
~opts=Some(opts),
|
|
150
|
+
)
|
|
149
151
|
|
|
150
152
|
// SNS → SQS subscription (raw delivery so body IS the event JSON)
|
|
151
153
|
let _subscription = Util_SQS.subscribeToSnsTopic(
|
|
@@ -85,30 +85,28 @@ function make(name, topicName, eventTopicOutputs, eventsApi, opts) {
|
|
|
85
85
|
sqsManagedSseEnabled: false
|
|
86
86
|
}, opts);
|
|
87
87
|
let snsResource = eventTopicOutputs.resources[0];
|
|
88
|
-
Pulumi.all([
|
|
88
|
+
let queuePolicyDocument = Pulumi.all([
|
|
89
89
|
queue.arn,
|
|
90
|
-
queue.id,
|
|
91
90
|
snsResource.urn
|
|
92
|
-
]).apply(param => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
});
|
|
91
|
+
]).apply(param => PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "EventLogSubQueuePolicy", [{
|
|
92
|
+
Sid: "AllowSNSSend",
|
|
93
|
+
Principal: {
|
|
94
|
+
Service: [AWS$ReventlessAws.SNS.principal]
|
|
95
|
+
},
|
|
96
|
+
Effect: "Allow",
|
|
97
|
+
Action: ["sqs:SendMessage"],
|
|
98
|
+
Resource: param[0],
|
|
99
|
+
Condition: {
|
|
100
|
+
ArnEquals: Object.fromEntries([[
|
|
101
|
+
"aws:SourceArn",
|
|
102
|
+
[param[1]]
|
|
103
|
+
]])
|
|
104
|
+
}
|
|
105
|
+
}])));
|
|
106
|
+
new (Aws.sqs.QueuePolicy)(name + "EventLogSubQueuePolicy", {
|
|
107
|
+
policy: queuePolicyDocument,
|
|
108
|
+
queueUrl: queue.id
|
|
109
|
+
}, opts);
|
|
112
110
|
Util_SQS$ReventlessAws.subscribeToSnsTopic(queue, name + "EventLogSub", name, snsResource, opts);
|
|
113
111
|
let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "EventLogSubRole", Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name + "EventLogSubRole", EventTopic$ReventlessCore.componentType, "Identity", undefined, name, undefined, undefined, undefined), opts);
|
|
114
112
|
Pulumi.all([
|
|
@@ -119,8 +119,10 @@ let generateAdminConfig = (
|
|
|
119
119
|
generateConfig(
|
|
120
120
|
~serverName=`${serverName}-admin`,
|
|
121
121
|
~serverVersion,
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
// Must match what Platform_Admin.construct registers — it forms the resource URI
|
|
123
|
+
// templates (`<pluginName>/<field>/{id}`) that MCP clients address.
|
|
124
|
+
~pluginName=ReventlessCore.Platform_Admin_Structure.pluginId,
|
|
125
|
+
~mutationEntries=ReventlessCore.Platform_AdminApi.mutationEntries(~cloner),
|
|
124
126
|
~queryEntries=ReventlessCore.PluginBaseFragment.queryEntries,
|
|
125
127
|
~commandTopicArns,
|
|
126
128
|
~queryDbTableNames,
|
|
@@ -11,11 +11,12 @@ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js
|
|
|
11
11
|
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
12
12
|
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
13
13
|
import * as Runtime$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Runtime/Runtime.res.mjs";
|
|
14
|
-
import * as AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/AdminApi.res.mjs";
|
|
15
14
|
import * as DynamoDb_Error$ReventlessAws from "../../errors/DynamoDb_Error.res.mjs";
|
|
15
|
+
import * as Platform_AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_AdminApi.res.mjs";
|
|
16
16
|
import * as PluginBaseFragment$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/api/PluginBaseFragment.res.mjs";
|
|
17
17
|
import * as MCP_SchemaGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/MCP_SchemaGenerator.res.mjs";
|
|
18
18
|
import * as Util_DynamoDb_Runtime$ReventlessAws from "../../util/Util_DynamoDb_Runtime.res.mjs";
|
|
19
|
+
import * as Platform_Admin_Structure$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_Admin_Structure.res.mjs";
|
|
19
20
|
import * as DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws from "../DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs";
|
|
20
21
|
import * as CommandTopicChannel_SQS_RuntimeResMjs from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
21
22
|
import * as CommandGenerator_CallbackResMjs from "@reventlessdev/reventless-core/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs";
|
|
@@ -69,7 +70,7 @@ function generateAdminConfig(serverName, serverVersion, clonerOpt, commandTopicA
|
|
|
69
70
|
let cloner = clonerOpt !== undefined ? clonerOpt : false;
|
|
70
71
|
let commandTopicArns = commandTopicArnsOpt !== undefined ? commandTopicArnsOpt : ({});
|
|
71
72
|
let queryDbTableNames = queryDbTableNamesOpt !== undefined ? queryDbTableNamesOpt : ({});
|
|
72
|
-
return generateConfig(serverName + `-admin`, serverVersion,
|
|
73
|
+
return generateConfig(serverName + `-admin`, serverVersion, Platform_Admin_Structure$ReventlessCore.pluginId, Platform_AdminApi$ReventlessCore.mutationEntries(cloner), PluginBaseFragment$ReventlessCore.queryEntries, undefined, commandTopicArns, queryDbTableNames, undefined);
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
function extractEntityId(uri) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Drains a Postgres DCB log via `PgChangeFeed` and pushes each event — transformed
|
|
4
4
|
// into the `{id, meta, event}` shape the plugin EventCollector SQS handler
|
|
5
|
-
// consumes — onto that queue. The plugin EventCollector (`
|
|
5
|
+
// consumes — onto that queue. The plugin EventCollector (`EventCollectorEntryPoint`)
|
|
6
6
|
// then drives the full fan-out: read-model projections, aggregate command topics,
|
|
7
7
|
// and the cross-plugin SNS EventTopic. See
|
|
8
8
|
// docs/plans/aws-postgres-change-feed-bridge.md.
|
package/src/adapter/Runtime/{AdminEventCollectorEntryPoint.mjs → EventCollectorEntryPoint.mjs}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Plugin EventCollector Lambda entry point — shared between the
|
|
2
|
-
// per-plugin EventCollector Lambda.
|
|
3
|
-
//
|
|
1
|
+
// Plugin EventCollector Lambda entry point — shared between the platform and every
|
|
2
|
+
// per-plugin EventCollector Lambda. This module is plugin-agnostic: behaviour is
|
|
3
|
+
// driven entirely by HANDLER_CONFIG.
|
|
4
4
|
//
|
|
5
5
|
// HANDLER_CONFIG schema (JSON in env var "HANDLER_CONFIG"):
|
|
6
6
|
//
|
|
@@ -411,7 +411,7 @@ function loadUiFragments() {
|
|
|
411
411
|
// Cross-plugin spec packages (e.g. "@reventlessdev/online-shop-hybrid-catalog-spec")
|
|
412
412
|
// are bundled into the function asset under /var/task/node_modules/ by
|
|
413
413
|
// PluginRuntime_Builder.forPluginEventCollector. This entry-point file, however,
|
|
414
|
-
// lives in the Lambda LAYER at /opt/nodejs/node_modules/.../
|
|
414
|
+
// lives in the Lambda LAYER at /opt/nodejs/node_modules/.../EventCollectorEntryPoint.mjs,
|
|
415
415
|
// so a bare `await import("@reventlessdev/online-shop-hybrid-catalog-spec/...")`
|
|
416
416
|
// from here resolves through /opt/nodejs/node_modules/ and never reaches the
|
|
417
417
|
// function asset. Anchoring createRequire at file:///var/task/index.mjs makes
|
|
@@ -585,7 +585,7 @@ async function buildHandler() {
|
|
|
585
585
|
// it through ExtensionMapping.Make, and build per-extension Extension_Operations.
|
|
586
586
|
const userExtensionHandlers = await Promise.all(config.extensions.map(async (ext) => {
|
|
587
587
|
if (!ext.specModule || !ext.mappingsModule || !ext.delegateModule) {
|
|
588
|
-
log.warn("extension '" + ext.name + "' missing module specifier(s); skipping. specModule=" + ext.specModule + ", mappingsModule=" + ext.mappingsModule + ", delegateModule=" + ext.delegateModule, { comp: "
|
|
588
|
+
log.warn("extension '" + ext.name + "' missing module specifier(s); skipping. specModule=" + ext.specModule + ", mappingsModule=" + ext.mappingsModule + ", delegateModule=" + ext.delegateModule, { comp: "EventCollectorEntryPoint" });
|
|
589
589
|
return null;
|
|
590
590
|
}
|
|
591
591
|
const epSpec = patchSpecId(await importFromAsset(ext.specModule));
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// STITCHED schema at push time — the additive counterpart of the neutral
|
|
8
8
|
// emission, mirroring how `injectAwsAuthAll`/`stampSharedIamTypes` decorate
|
|
9
9
|
// auth. Runtime-pure so the bundled Lambda entry points
|
|
10
|
-
// (
|
|
10
|
+
// (EventCollectorEntryPoint.mjs) can import it without dragging
|
|
11
11
|
// deploy-time dependencies into the runtime module graph.
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -375,7 +375,7 @@ export function response(ctx) {
|
|
|
375
375
|
}
|
|
376
376
|
`->Pulumi.Input.make
|
|
377
377
|
|
|
378
|
-
let field = ReventlessCore.
|
|
378
|
+
let field = ReventlessCore.Platform_AdminApi.cloneMutationEntry.fieldNames->Array.getUnsafe(0)
|
|
379
379
|
let resolver = AppSync_Resolver_Retrying.makeUnitJsResolver(
|
|
380
380
|
~name=field,
|
|
381
381
|
~api,
|
|
@@ -11,11 +11,11 @@ import * as AWS$ReventlessAws from "../../adapter/AWS.res.mjs";
|
|
|
11
11
|
import * as Cloner$ReventlessCore from "@reventlessdev/reventless-core/src/components/Cloner.res.mjs";
|
|
12
12
|
import * as AWS_Tags$ReventlessAws from "../../adapter/AWS_Tags.res.mjs";
|
|
13
13
|
import * as Util_Vpc$ReventlessAws from "../../util/Util_Vpc.res.mjs";
|
|
14
|
-
import * as AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/AdminApi.res.mjs";
|
|
15
14
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
16
15
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
17
16
|
import * as GetSecretVersion$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/SecretsManager/GetSecretVersion.res.mjs";
|
|
18
17
|
import * as Util_AppSync$ReventlessAws from "../../util/Util_AppSync.res.mjs";
|
|
18
|
+
import * as Platform_AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_AdminApi.res.mjs";
|
|
19
19
|
import * as AppSync_Resolver_Retrying$ReventlessAws from "../../adapter/Api/AppSync_Resolver_Retrying.res.mjs";
|
|
20
20
|
|
|
21
21
|
function make(name, api, fullQualifiedStackName, reventlessCiSecretUrn, secretUrns, opts) {
|
|
@@ -226,7 +226,7 @@ export const handler = async (event) => {
|
|
|
226
226
|
},
|
|
227
227
|
serviceRoleArn: dataSourceRole.arn
|
|
228
228
|
}, opts);
|
|
229
|
-
let field =
|
|
229
|
+
let field = Platform_AdminApi$ReventlessCore.cloneMutationEntry.fieldNames[0];
|
|
230
230
|
let resolver = AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(field, api, dataSource.name, "Mutation", field, `import { util } from '@aws-appsync/utils';
|
|
231
231
|
export function request(ctx) {
|
|
232
232
|
return {
|
|
@@ -118,7 +118,7 @@ let registerConfig = (
|
|
|
118
118
|
|
|
119
119
|
// PluginRuntime_Builder is a functor so that the caller can inject the EventCollectorChannel
|
|
120
120
|
// implementation. All other runtime builders hardcode EventCollectorChannel.DynamoDbStream, but
|
|
121
|
-
// the Plugin admin handler (
|
|
121
|
+
// the Plugin admin handler (EventCollectorEntryPoint) is shared across DCB and aggregate
|
|
122
122
|
// plugins which may use different event log backends. The functor keeps the builder generic
|
|
123
123
|
// while the concrete instantiation picks the right channel at the platform level.
|
|
124
124
|
module Make = (
|
|
@@ -151,9 +151,11 @@ module Make = (
|
|
|
151
151
|
let synthesizeAdminContext = (): ReventlessCore.Plugin_Helpers.eventCollectorContext => {
|
|
152
152
|
let config = configRef.contents
|
|
153
153
|
// Stable JSON literal — matches Reventless.Plugin.pluginDefinitionSchema
|
|
154
|
-
// (optional fields encoded as null via js_nullable).
|
|
154
|
+
// (optional fields encoded as null via js_nullable). Must stay byte-aligned with
|
|
155
|
+
// the ReScript-built twin in `Platform_Admin.construct`'s `fakePluginDefinition`.
|
|
156
|
+
let platformId = ReventlessCore.Platform_Admin_Structure.pluginId
|
|
155
157
|
let fakePluginDefinitionJson =
|
|
156
|
-
`{"id":"
|
|
158
|
+
`{"id":"${platformId}@INTERNAL","name":"${platformId}","version":"INTERNAL","extensionPoints":[],"extensions":[],"eventCollector":"NOT-SET","extensionProtocols":[],"apiSchemaFragment":null,"apiTarget":null,"structure":null}`->Pulumi.Output.make
|
|
157
159
|
let adminEpEventTopicArn = switch config.eventTopicArn {
|
|
158
160
|
| Some(arn) => arn
|
|
159
161
|
| None => Pulumi.Output.make("NOT_AVAILABLE")
|
|
@@ -436,7 +438,7 @@ module Make = (
|
|
|
436
438
|
[ext.specModule, ext.mappingsModule, ext.delegateModule]->Array.forEach(addPackageFor)
|
|
437
439
|
})
|
|
438
440
|
// Outgoing EPs: the entry point dynamic-imports ep.specModule and
|
|
439
|
-
// ep.mappingsModule at cold start (
|
|
441
|
+
// ep.mappingsModule at cold start (EventCollectorEntryPoint.mjs).
|
|
440
442
|
// Without this walk the EP spec package (e.g. <plugin>-spec) is missing
|
|
441
443
|
// from the asset zip even though HANDLER_CONFIG references it, and the
|
|
442
444
|
// Lambda crashes with MODULE_NOT_FOUND before any event is processed.
|
|
@@ -484,7 +486,9 @@ module Make = (
|
|
|
484
486
|
// extension can emit RegisterUiFragment in the handshake answer.
|
|
485
487
|
extraStringAssets->Dict.set("uiFragments.json", uiFragmentsJson)
|
|
486
488
|
Util_Bundle.buildCodeArchive(
|
|
487
|
-
|
|
489
|
+
// Path string, not a module reference — a rename that misses this stays green
|
|
490
|
+
// at build time and fails at bundle time or Lambda cold start.
|
|
491
|
+
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/EventCollectorEntryPoint.mjs",
|
|
488
492
|
~packageDirs,
|
|
489
493
|
~extraStringAssets,
|
|
490
494
|
)
|
|
@@ -15,6 +15,7 @@ import * as ComponentType$ReventlessCore from "@reventlessdev/reventless-core/sr
|
|
|
15
15
|
import * as EventCollector$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventCollector/EventCollector.res.mjs";
|
|
16
16
|
import * as EventLogBackend$ReventlessAws from "../../adapter/EventLog/EventLogBackend.res.mjs";
|
|
17
17
|
import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
|
|
18
|
+
import * as Platform_Admin_Structure$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_Admin_Structure.res.mjs";
|
|
18
19
|
import * as RuntimeEnvironment_Lambda$ReventlessAws from "../../adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs";
|
|
19
20
|
import * as StateChangeSliceRuntime_Builder_Single$ReventlessAws from "../../adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs";
|
|
20
21
|
|
|
@@ -132,7 +133,7 @@ function Make(EventCollectorChannel) {
|
|
|
132
133
|
};
|
|
133
134
|
let synthesizeAdminContext = () => {
|
|
134
135
|
let config = configRef.contents;
|
|
135
|
-
let fakePluginDefinitionJson = Pulumi.output(`{"id":"
|
|
136
|
+
let fakePluginDefinitionJson = Pulumi.output(`{"id":"` + Platform_Admin_Structure$ReventlessCore.pluginId + `@INTERNAL","name":"` + Platform_Admin_Structure$ReventlessCore.pluginId + `","version":"INTERNAL","extensionPoints":[],"extensions":[],"eventCollector":"NOT-SET","extensionProtocols":[],"apiSchemaFragment":null,"apiTarget":null,"structure":null}`);
|
|
136
137
|
let arn = config.eventTopicArn;
|
|
137
138
|
let adminEpEventTopicArn = arn !== undefined ? arn : Pulumi.output("NOT_AVAILABLE");
|
|
138
139
|
return {
|
|
@@ -306,7 +307,7 @@ function Make(EventCollectorChannel) {
|
|
|
306
307
|
let extraStringAssets = {};
|
|
307
308
|
extraStringAssets["pluginDefinition.json"] = param[0];
|
|
308
309
|
extraStringAssets["uiFragments.json"] = param[1];
|
|
309
|
-
return Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/
|
|
310
|
+
return Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/EventCollectorEntryPoint.mjs", packageDirs, extraStringAssets);
|
|
310
311
|
});
|
|
311
312
|
let codeOutput = bundleOutput.apply(b => b.code);
|
|
312
313
|
let sourceCodeHashOutput = bundleOutput.apply(b => b.sourceCodeHash);
|
|
@@ -122,7 +122,7 @@ let createQueuePolicyDocument = (name, queueArn: string, handlerArn: string) =>
|
|
|
122
122
|
let _ =
|
|
123
123
|
(queue.id, queue.arn, fifoQueue.id, fifoQueue.arn, handler.arn)
|
|
124
124
|
->Pulumi.Output.all5
|
|
125
|
-
->Pulumi.Output.apply(((
|
|
125
|
+
->Pulumi.Output.apply(((_queueId, queueArn, _fifoQueueId, fifoQueueArn, handlerArn)) => {
|
|
126
126
|
let lambdaPolicyDocument = PulumiAws.PolicyDocument.make(
|
|
127
127
|
~id=name ++ "SQSPolicy",
|
|
128
128
|
~statements=[
|
|
@@ -152,10 +152,20 @@ let _ =
|
|
|
152
152
|
~opts,
|
|
153
153
|
)
|
|
154
154
|
|
|
155
|
+
// `queueUrl` takes the queue's Output, not the resolved `queueId` string:
|
|
156
|
+
// that is what registers the policy -> queue dependency, without which
|
|
157
|
+
// Pulumi has no ordering constraint and can delete the queue first on a
|
|
158
|
+
// replacement, leaving the policy's delete polling a queue that is gone.
|
|
159
|
+
//
|
|
160
|
+
// Unlike the channel helpers, these two stay inside the apply. This module
|
|
161
|
+
// creates its resources at import time and this apply resolves late enough
|
|
162
|
+
// to race Jest's teardown; reshaping it (dropping the now-unused ids to
|
|
163
|
+
// narrow the tuple) shifts that timing and crashes the AWS unit suites, so
|
|
164
|
+
// `_queueId` / `_fifoQueueId` are deliberately kept.
|
|
155
165
|
let _attachQueuePolicy = PulumiAws.SQS.QueuePolicy.make(
|
|
156
166
|
~name,
|
|
157
167
|
~args={
|
|
158
|
-
queueUrl:
|
|
168
|
+
queueUrl: queue.id->Pulumi.Output.asInput,
|
|
159
169
|
policy: createQueuePolicyDocument(name, queueArn, handlerArn),
|
|
160
170
|
},
|
|
161
171
|
~opts=Some(opts),
|
|
@@ -163,7 +173,7 @@ let _ =
|
|
|
163
173
|
let _attachFifoQueuePolicy = PulumiAws.SQS.QueuePolicy.make(
|
|
164
174
|
~name=nameFifo,
|
|
165
175
|
~args={
|
|
166
|
-
queueUrl:
|
|
176
|
+
queueUrl: fifoQueue.id->Pulumi.Output.asInput,
|
|
167
177
|
policy: createQueuePolicyDocument(nameFifo, fifoQueueArn, handlerArn),
|
|
168
178
|
},
|
|
169
179
|
~opts=Some(opts),
|
|
@@ -132,11 +132,11 @@ Pulumi.all([
|
|
|
132
132
|
}, opts);
|
|
133
133
|
new (Aws.sqs.QueuePolicy)(name, {
|
|
134
134
|
policy: createQueuePolicyDocument(name, queueArn, handlerArn),
|
|
135
|
-
queueUrl:
|
|
135
|
+
queueUrl: queue.id
|
|
136
136
|
}, opts);
|
|
137
137
|
new (Aws.sqs.QueuePolicy)(nameFifo, {
|
|
138
138
|
policy: createQueuePolicyDocument(nameFifo, fifoQueueArn, handlerArn),
|
|
139
|
-
queueUrl:
|
|
139
|
+
queueUrl: fifoQueue.id
|
|
140
140
|
}, opts);
|
|
141
141
|
});
|
|
142
142
|
|
|
@@ -23,7 +23,7 @@ let decodeFragment = (fragment: Reventless.Plugin.apiSchemaFragment) =>
|
|
|
23
23
|
ReventlessCore.GraphQL_Stitcher.decode(fragment)
|
|
24
24
|
|
|
25
25
|
describe("AppSync_Adapter.injectAwsAuthAll", () => {
|
|
26
|
-
let baseFragment = ReventlessCore.
|
|
26
|
+
let baseFragment = ReventlessCore.Platform_AdminApi.baseFragment(~cloner=true)
|
|
27
27
|
|
|
28
28
|
testSync("adds @aws_auth directive to all mutation fields", () => {
|
|
29
29
|
let augmented = AppSync_Adapter.injectAwsAuthAll(baseFragment, ~group="Admin")
|
|
@@ -86,10 +86,10 @@ describe("AppSync_Adapter.injectAwsAuth", () => {
|
|
|
86
86
|
testSync("injects auth only on entries with authorization", () => {
|
|
87
87
|
// Build a fragment with known entries
|
|
88
88
|
let mutationEntries: array<ReventlessInfra.Api.mutationSchemaEntry> =
|
|
89
|
-
ReventlessCore.
|
|
89
|
+
ReventlessCore.Platform_AdminApi.mutationEntries(~cloner=false)
|
|
90
90
|
let queryEntries = ReventlessCore.PluginBaseFragment.queryEntries
|
|
91
91
|
|
|
92
|
-
let baseFragment = ReventlessCore.
|
|
92
|
+
let baseFragment = ReventlessCore.Platform_AdminApi.baseFragment(~cloner=false)
|
|
93
93
|
let augmented = AppSync_Adapter.injectAwsAuth(
|
|
94
94
|
baseFragment,
|
|
95
95
|
~mutationEntries,
|
|
@@ -111,7 +111,7 @@ describe("AppSync_Adapter.injectAwsAuth", () => {
|
|
|
111
111
|
|
|
112
112
|
describe("AppSync_Adapter.generateFragment", () => {
|
|
113
113
|
testSync("produces fragment with auth directives from entries", () => {
|
|
114
|
-
let mutationEntries = ReventlessCore.
|
|
114
|
+
let mutationEntries = ReventlessCore.Platform_AdminApi.mutationEntries(~cloner=false)
|
|
115
115
|
let queryEntries = ReventlessCore.PluginBaseFragment.queryEntries
|
|
116
116
|
|
|
117
117
|
let fragment = AppSync_Adapter.generateFragment(~mutationEntries, ~queryEntries)
|
|
@@ -570,7 +570,7 @@ describe("AppSync_Adapter — type-level dual-auth", () => {
|
|
|
570
570
|
})
|
|
571
571
|
|
|
572
572
|
describe("AppSync_Adapter.injectAwsAuthAll — ~iamFieldNames", () => {
|
|
573
|
-
let baseFragment = ReventlessCore.
|
|
573
|
+
let baseFragment = ReventlessCore.Platform_AdminApi.baseFragment(~cloner=true)
|
|
574
574
|
|
|
575
575
|
testSync("marks only the named field dual-auth, leaving others single-mode", () => {
|
|
576
576
|
// Pick a real mutation field name from the admin base fragment.
|
|
@@ -644,7 +644,7 @@ describe("Split mode — empty base fragment", () => {
|
|
|
644
644
|
})
|
|
645
645
|
|
|
646
646
|
testSync("stitching admin base without plugins produces only admin fields", () => {
|
|
647
|
-
let adminBase = ReventlessCore.
|
|
647
|
+
let adminBase = ReventlessCore.Platform_AdminApi.baseFragment(~cloner=false)
|
|
648
648
|
let sdl = ReventlessCore.GraphQL_Stitcher.stitch(
|
|
649
649
|
~baseFragment=adminBase,
|
|
650
650
|
~pluginFragments=[],
|
|
@@ -678,7 +678,7 @@ describe("Merged mode — canonical source documents", () => {
|
|
|
678
678
|
|
|
679
679
|
let adminSourceSdl = assembleCanonicalSourceSdl(
|
|
680
680
|
~baseFragment=AppSync_Adapter.injectAwsAuthAll(
|
|
681
|
-
ReventlessCore.
|
|
681
|
+
ReventlessCore.Platform_AdminApi.baseFragment(~cloner=false),
|
|
682
682
|
~group="Admin",
|
|
683
683
|
),
|
|
684
684
|
)
|
|
@@ -5,9 +5,9 @@ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
7
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
8
|
-
import * as AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/AdminApi.res.mjs";
|
|
9
8
|
import * as AppSync_Adapter$ReventlessAws from "../src/components/Api/AppSync_Adapter.res.mjs";
|
|
10
9
|
import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
10
|
+
import * as Platform_AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_AdminApi.res.mjs";
|
|
11
11
|
import * as AppSync_SdlDecorate$ReventlessAws from "../src/components/Api/AppSync_SdlDecorate.res.mjs";
|
|
12
12
|
import * as PluginBaseFragment$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/api/PluginBaseFragment.res.mjs";
|
|
13
13
|
|
|
@@ -28,7 +28,7 @@ globalThis.describe("AppSync_Adapter.sha256Hex", () => {
|
|
|
28
28
|
let decodeFragment = GraphQL_Stitcher$ReventlessCore.decode;
|
|
29
29
|
|
|
30
30
|
globalThis.describe("AppSync_Adapter.injectAwsAuthAll", () => {
|
|
31
|
-
let baseFragment =
|
|
31
|
+
let baseFragment = Platform_AdminApi$ReventlessCore.baseFragment(true);
|
|
32
32
|
globalThis.test("adds @aws_auth directive to all mutation fields", () => {
|
|
33
33
|
let augmented = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(baseFragment, "Admin", undefined);
|
|
34
34
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(augmented);
|
|
@@ -77,8 +77,8 @@ globalThis.describe("AppSync_Adapter.injectAwsAuthAll", () => {
|
|
|
77
77
|
|
|
78
78
|
globalThis.describe("AppSync_Adapter.injectAwsAuth", () => {
|
|
79
79
|
globalThis.test("injects auth only on entries with authorization", () => {
|
|
80
|
-
let mutationEntries =
|
|
81
|
-
let baseFragment =
|
|
80
|
+
let mutationEntries = Platform_AdminApi$ReventlessCore.mutationEntries(false);
|
|
81
|
+
let baseFragment = Platform_AdminApi$ReventlessCore.baseFragment(false);
|
|
82
82
|
let augmented = AppSync_Adapter$ReventlessAws.injectAwsAuth(baseFragment, mutationEntries, PluginBaseFragment$ReventlessCore.queryEntries);
|
|
83
83
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(augmented);
|
|
84
84
|
let queryWithAuth = PluginBaseFragment$ReventlessCore.queryEntries.some(entry => Stdlib_Option.isSome(entry.authorization));
|
|
@@ -92,7 +92,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth", () => {
|
|
|
92
92
|
|
|
93
93
|
globalThis.describe("AppSync_Adapter.generateFragment", () => {
|
|
94
94
|
globalThis.test("produces fragment with auth directives from entries", () => {
|
|
95
|
-
let mutationEntries =
|
|
95
|
+
let mutationEntries = Platform_AdminApi$ReventlessCore.mutationEntries(false);
|
|
96
96
|
let fragment = AppSync_Adapter$ReventlessAws.generateFragment(mutationEntries, PluginBaseFragment$ReventlessCore.queryEntries);
|
|
97
97
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
|
|
98
98
|
globalThis.expect(parts.types.length).toBeGreaterThan(0);
|
|
@@ -536,7 +536,7 @@ globalThis.describe("AppSync_Adapter — type-level dual-auth", () => {
|
|
|
536
536
|
});
|
|
537
537
|
|
|
538
538
|
globalThis.describe("AppSync_Adapter.injectAwsAuthAll — ~iamFieldNames", () => {
|
|
539
|
-
let baseFragment =
|
|
539
|
+
let baseFragment = Platform_AdminApi$ReventlessCore.baseFragment(true);
|
|
540
540
|
globalThis.test("marks only the named field dual-auth, leaving others single-mode", () => {
|
|
541
541
|
let firstMutationName = GraphQL_Stitcher$ReventlessCore.extractLeadingName(GraphQL_Stitcher$ReventlessCore.decode(baseFragment).mutations[0]);
|
|
542
542
|
let augmented = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(baseFragment, "Admin", [firstMutationName]);
|
|
@@ -600,7 +600,7 @@ globalThis.describe("Split mode — empty base fragment", () => {
|
|
|
600
600
|
globalThis.expect(sdl).not.toContain("Platform_Plugin");
|
|
601
601
|
});
|
|
602
602
|
globalThis.test("stitching admin base without plugins produces only admin fields", () => {
|
|
603
|
-
let adminBase =
|
|
603
|
+
let adminBase = Platform_AdminApi$ReventlessCore.baseFragment(false);
|
|
604
604
|
let sdl = GraphQL_Stitcher$ReventlessCore.stitch(adminBase, []);
|
|
605
605
|
globalThis.expect(sdl).toContain("Platform_Plugin");
|
|
606
606
|
});
|
|
@@ -614,7 +614,7 @@ globalThis.describe("Merged mode — canonical source documents", () => {
|
|
|
614
614
|
subscriptions: [],
|
|
615
615
|
subscriptionSources: []
|
|
616
616
|
});
|
|
617
|
-
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(
|
|
617
|
+
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(Platform_AdminApi$ReventlessCore.baseFragment(false), "Admin", undefined);
|
|
618
618
|
let adminSourceSdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(baseFragment));
|
|
619
619
|
let domainBaseSourceSdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(domainBaseFragment));
|
|
620
620
|
globalThis.test("no AWS source document carries the global node query (dropped — see plan)", () => {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
// Guards the CommandTopic queue policy's service-principal scoping. The
|
|
4
|
+
// EventBridge grant cannot name a single rule ARN — ScheduledPublisher creates
|
|
5
|
+
// rules at runtime and EventBridge authorises SQS targets against the queue's
|
|
6
|
+
// resource policy — so the condition keys are the only bound on who may send
|
|
7
|
+
// into a command topic. A dropped condition is invisible at deploy time.
|
|
8
|
+
|
|
9
|
+
let queueArn = "arn:aws:sqs:eu-west-1:123456789012:CatalogAggrCmdTopic-5150050"
|
|
10
|
+
let lambdaArn = "arn:aws:lambda:eu-west-1:123456789012:function:AllAggregates-1a2b3c4"
|
|
11
|
+
|
|
12
|
+
let policy =
|
|
13
|
+
CommandTopicChannel_Helpers.createQueuePolicyDocument(
|
|
14
|
+
~name="CatalogAggrCmdTopic",
|
|
15
|
+
~queueArn,
|
|
16
|
+
~lambdaArn,
|
|
17
|
+
)->JSON.parseOrThrow
|
|
18
|
+
|
|
19
|
+
let field = (json, key) =>
|
|
20
|
+
json->JSON.Decode.object->Option.flatMap(o => o->Dict.get(key))
|
|
21
|
+
|
|
22
|
+
let statement = sid =>
|
|
23
|
+
policy
|
|
24
|
+
->field("Statement")
|
|
25
|
+
->Option.flatMap(JSON.Decode.array)
|
|
26
|
+
->Option.getOr([])
|
|
27
|
+
->Array.find(s => s->field("Sid")->Option.flatMap(JSON.Decode.string) == Some(sid))
|
|
28
|
+
|
|
29
|
+
let condition = (sid, operator, key) =>
|
|
30
|
+
statement(sid)
|
|
31
|
+
->Option.flatMap(s => s->field("Condition"))
|
|
32
|
+
->Option.flatMap(c => c->field(operator))
|
|
33
|
+
->Option.flatMap(o => o->field(key))
|
|
34
|
+
->Option.flatMap(JSON.Decode.string)
|
|
35
|
+
|
|
36
|
+
describe("CommandTopic queue policy — EventBridge grant", () => {
|
|
37
|
+
testSync("is scoped to this account", () => {
|
|
38
|
+
expect(
|
|
39
|
+
condition("AllowCloudWatchEventsToSendToQueue", "StringEquals", "aws:SourceAccount"),
|
|
40
|
+
)->toEqual(Some("123456789012"))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
testSync("is scoped to EventBridge rule ARNs in this account", () => {
|
|
44
|
+
expect(condition("AllowCloudWatchEventsToSendToQueue", "ArnLike", "aws:SourceArn"))->toEqual(
|
|
45
|
+
Some("arn:aws:events:*:123456789012:rule/*"),
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
testSync("names the EventBridge service principal", () => {
|
|
50
|
+
let principal =
|
|
51
|
+
statement("AllowCloudWatchEventsToSendToQueue")
|
|
52
|
+
->Option.flatMap(s => s->field("Principal"))
|
|
53
|
+
->Option.flatMap(p => p->field("Service"))
|
|
54
|
+
->Option.flatMap(JSON.Decode.string)
|
|
55
|
+
expect(principal)->toEqual(Some("events.amazonaws.com"))
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe("CommandTopic queue policy — Lambda grant", () => {
|
|
60
|
+
testSync("stays pinned to the consuming function ARN", () => {
|
|
61
|
+
expect(condition("AllowLambdaToAccessQueue", "ArnEquals", "AWS:SourceArn"))->toEqual(
|
|
62
|
+
Some(lambdaArn),
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
6
|
+
import * as CommandTopicChannel_Helpers$ReventlessAws from "../src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs";
|
|
7
|
+
|
|
8
|
+
let queueArn = "arn:aws:sqs:eu-west-1:123456789012:CatalogAggrCmdTopic-5150050";
|
|
9
|
+
|
|
10
|
+
let lambdaArn = "arn:aws:lambda:eu-west-1:123456789012:function:AllAggregates-1a2b3c4";
|
|
11
|
+
|
|
12
|
+
let policy = JSON.parse(CommandTopicChannel_Helpers$ReventlessAws.createQueuePolicyDocument("CatalogAggrCmdTopic", queueArn, lambdaArn));
|
|
13
|
+
|
|
14
|
+
function field(json, key) {
|
|
15
|
+
return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), o => o[key]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function statement(sid) {
|
|
19
|
+
return Stdlib_Option.getOr(Stdlib_Option.flatMap(field(policy, "Statement"), Stdlib_JSON.Decode.array), []).find(s => Primitive_object.equal(Stdlib_Option.flatMap(field(s, "Sid"), Stdlib_JSON.Decode.string), sid));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function condition(sid, operator, key) {
|
|
23
|
+
return Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(statement(sid), s => field(s, "Condition")), c => field(c, operator)), o => field(o, key)), Stdlib_JSON.Decode.string);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
globalThis.describe("CommandTopic queue policy — EventBridge grant", () => {
|
|
27
|
+
globalThis.test("is scoped to this account", () => {
|
|
28
|
+
globalThis.expect(condition("AllowCloudWatchEventsToSendToQueue", "StringEquals", "aws:SourceAccount")).toEqual("123456789012");
|
|
29
|
+
});
|
|
30
|
+
globalThis.test("is scoped to EventBridge rule ARNs in this account", () => {
|
|
31
|
+
globalThis.expect(condition("AllowCloudWatchEventsToSendToQueue", "ArnLike", "aws:SourceArn")).toEqual("arn:aws:events:*:123456789012:rule/*");
|
|
32
|
+
});
|
|
33
|
+
globalThis.test("names the EventBridge service principal", () => {
|
|
34
|
+
let principal = Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(statement("AllowCloudWatchEventsToSendToQueue"), s => field(s, "Principal")), p => field(p, "Service")), Stdlib_JSON.Decode.string);
|
|
35
|
+
globalThis.expect(principal).toEqual("events.amazonaws.com");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
globalThis.describe("CommandTopic queue policy — Lambda grant", () => {
|
|
40
|
+
globalThis.test("stays pinned to the consuming function ARN", () => {
|
|
41
|
+
globalThis.expect(condition("AllowLambdaToAccessQueue", "ArnEquals", "AWS:SourceArn")).toEqual(lambdaArn);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
queueArn,
|
|
47
|
+
lambdaArn,
|
|
48
|
+
policy,
|
|
49
|
+
field,
|
|
50
|
+
statement,
|
|
51
|
+
condition,
|
|
52
|
+
}
|
|
53
|
+
/* policy Not a pure module */
|
package/tests/MCP_LambdaTest.res
CHANGED
|
@@ -73,7 +73,7 @@ describe("MCP_Lambda.generateAdminConfig", () => {
|
|
|
73
73
|
let arns = Dict.make()
|
|
74
74
|
// Use a tool name that matches the first mutation field
|
|
75
75
|
let firstEntry: ReventlessInfra.Api.mutationSchemaEntry =
|
|
76
|
-
ReventlessCore.
|
|
76
|
+
ReventlessCore.Platform_AdminApi.mutationEntries(~cloner=false)->Array.getUnsafe(0)
|
|
77
77
|
let firstFieldName = firstEntry.fieldNames->Array.getUnsafe(0)
|
|
78
78
|
arns->Dict.set(firstFieldName, "arn:aws:sqs:us-east-1:123:queue")
|
|
79
79
|
|