@reventlessdev/reventless-aws 3.0.0-alpha.223 → 3.0.0-alpha.225
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 +17 -0
- package/package.json +6 -6
- package/src/adapter/AWS_Tags.res +36 -0
- package/src/adapter/Adapter_Helpers.res +6 -0
- package/src/adapter/Adapter_Helpers.res.mjs +6 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res +77 -48
- package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs +55 -40
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +6 -0
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +3 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +29 -6
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +13 -1
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res +83 -60
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res.mjs +20 -18
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +44 -30
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +22 -23
- package/src/adapter/Postgres/PgProjectionFeed.res +20 -1
- package/src/adapter/Postgres/PgProjectionFeed.res.mjs +3 -1
- package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +6 -2
- package/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs +9 -2
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +10 -4
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res +8 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +4 -2
- package/src/plugin/cloner/ClonerRunner_Fargate.res +6 -0
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +2 -1
- package/src/util/Util_DeadLetterQueue.res +39 -4
- package/src/util/Util_DeadLetterQueue.res.mjs +16 -4
- package/src/util/Util_EventSourceMapping.res +8 -0
- package/src/util/Util_EventSourceMapping.res.mjs +6 -4
- package/tests/CommandTopicQueuePolicyTest.res +65 -0
- package/tests/CommandTopicQueuePolicyTest.res.mjs +53 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +50 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +47 -0
- package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res +58 -0
- package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res.mjs +78 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.225 (2026-07-22)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** keep an empty DCB tag value from breaking the append ([1c0ab40](https://github.com/ReventlessDev/reventless-core/commit/1c0ab40f25b596702a81f42686d77c5e423e9b44))
|
|
11
|
+
* **aws:** key the disconnect schedule's rule name to the stack, not the Lambda ([05aebcf](https://github.com/ReventlessDev/reventless-core/commit/05aebcfacac34fd9c7cb42f353ee91e20066067e))
|
|
12
|
+
* **aws:** tag every taggable resource the framework still left bare ([d4f7a90](https://github.com/ReventlessDev/reventless-core/commit/d4f7a908bcaf686ff59a85e300efac4e5e6199df))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# 3.0.0-alpha.224 (2026-07-22)
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **aws:** register the QueuePolicy -> Queue dependency so destroys order correctly ([81cf2b3](https://github.com/ReventlessDev/reventless-core/commit/81cf2b3198fea7ad280817d2414102089ba0f13d))
|
|
20
|
+
* **aws:** scope the CommandTopic queue policy's EventBridge send grant ([2f5f148](https://github.com/ReventlessDev/reventless-core/commit/2f5f148f4d73b2997633207f071c8d055960cc75))
|
|
21
|
+
|
|
22
|
+
|
|
6
23
|
# 3.0.0-alpha.223 (2026-07-22)
|
|
7
24
|
|
|
8
25
|
* refactor!: retire the stale "Admin" vocabulary for platform-owned resources ([2770bd9](https://github.com/ReventlessDev/reventless-core/commit/2770bd96f309142bdfc65b9f36bd1a3ef60641bd))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.225",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.30",
|
|
13
13
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.9",
|
|
14
14
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
|
|
15
|
-
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.57",
|
|
16
15
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.17",
|
|
17
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.
|
|
18
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.80",
|
|
16
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.178",
|
|
19
17
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.29",
|
|
20
|
-
"@reventlessdev/reventless-
|
|
21
|
-
"@reventlessdev/reventless-
|
|
18
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.104",
|
|
19
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.42",
|
|
20
|
+
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.58",
|
|
21
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.80"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"rescript": "12.3.0",
|
package/src/adapter/AWS_Tags.res
CHANGED
|
@@ -103,6 +103,42 @@ let makeDict = (
|
|
|
103
103
|
]->Dict.fromArray
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/***
|
|
107
|
+
Framework-created AWS resource types that carry NO attribution tags, and why.
|
|
108
|
+
Keep this list here rather than in a plan: it is what tells a coverage audit
|
|
109
|
+
whether a bare resource is a gap to close or a fact to work around.
|
|
110
|
+
|
|
111
|
+
**Cannot be tagged** — the provider type exposes no `tags` property at all
|
|
112
|
+
(verified against `@pulumi/aws` 7.19.0; re-verify on a major provider bump, since
|
|
113
|
+
AWS does add tag support over time — `lambda/eventSourceMapping` gained it after
|
|
114
|
+
the first sweep and is now tagged):
|
|
115
|
+
|
|
116
|
+
aws:appsync/dataSource aws:appsync/resolver (incl. aws-native)
|
|
117
|
+
aws:appsync/function aws:appsync/sourceApiAssociation
|
|
118
|
+
aws:iam/rolePolicy aws:iam/rolePolicyAttachment
|
|
119
|
+
aws:sqs/queuePolicy aws:sns/topicSubscription
|
|
120
|
+
aws:lambda/permission aws:lambda/invocation
|
|
121
|
+
aws:cloudwatch/logMetricFilter aws:cloudwatch/eventTarget
|
|
122
|
+
aws:s3/bucketPolicy aws:s3/bucketPublicAccessBlock
|
|
123
|
+
aws:cloudfront/originAccessControl
|
|
124
|
+
aws:cognito/userPoolClient aws:cognito/userGroup
|
|
125
|
+
aws:route53/record aws:acm/certificateValidation
|
|
126
|
+
aws:ses/emailIdentity aws:ses/identityPolicy
|
|
127
|
+
|
|
128
|
+
Each is attributable only through its parent (function, log group, API, role,
|
|
129
|
+
bucket, pool, certificate) — i.e. structurally, by URN ancestry. A consumer that
|
|
130
|
+
treats "carries `reventless:*` tags" as universal will be wrong about every row
|
|
131
|
+
above, and no amount of coverage work changes that. Note the ReScript binding for
|
|
132
|
+
`IAM.RolePolicy` carries a `tags?` field that AWS ignores; do not read its
|
|
133
|
+
presence as tag support.
|
|
134
|
+
|
|
135
|
+
**Taggable, deliberately untagged** — `aws:s3/bucketObject`. Static-bundle assets
|
|
136
|
+
are bucket *contents*, not infrastructure: they are fully attributed by the
|
|
137
|
+
bucket that holds them, and S3 bills object tags per object per month, so tagging
|
|
138
|
+
a few hundred bundle files would cost money to restate a fact the parent already
|
|
139
|
+
carries.
|
|
140
|
+
*/
|
|
141
|
+
|
|
106
142
|
/**
|
|
107
143
|
The same schema as `makeDict`, wrapped as a Pulumi input — what nearly every
|
|
108
144
|
resource's `tags` field expects. Use `makeDict` only for the bindings that take
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// SQS queue ARN is `arn:aws:sqs:<region>:<accountId>:<name>` — segment index 4
|
|
2
|
+
// (0-based). Used to scope queue-policy service principals to this account via
|
|
3
|
+
// the aws:SourceAccount condition key.
|
|
4
|
+
let accountIdOfQueueArn = (queueArn: string) =>
|
|
5
|
+
queueArn->String.split(":")->Array.get(4)->Option.getOr("")
|
|
6
|
+
|
|
1
7
|
let dynamoDbStreamResources = eventTopicResources =>
|
|
2
8
|
eventTopicResources->ReventlessCore.Util.Adapter.filterSupportedResolvedResources([
|
|
3
9
|
AWS.DynamoDbStream.service,
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
3
4
|
import * as AWS$ReventlessAws from "./AWS.res.mjs";
|
|
4
5
|
import * as Util_Adapter$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Adapter.res.mjs";
|
|
5
6
|
|
|
7
|
+
function accountIdOfQueueArn(queueArn) {
|
|
8
|
+
return Stdlib_Option.getOr(queueArn.split(":")[4], "");
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
function dynamoDbStreamResources(eventTopicResources) {
|
|
7
12
|
return Util_Adapter$ReventlessCore.filterSupportedResolvedResources(eventTopicResources, [AWS$ReventlessAws.DynamoDbStream.service]);
|
|
8
13
|
}
|
|
@@ -29,6 +34,7 @@ function dynamoDbResources(resources) {
|
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
export {
|
|
37
|
+
accountIdOfQueueArn,
|
|
32
38
|
dynamoDbStreamResources,
|
|
33
39
|
snsResources,
|
|
34
40
|
sqsResources,
|
|
@@ -1,55 +1,73 @@
|
|
|
1
1
|
open PulumiAws.PolicyDocument
|
|
2
2
|
open Adapter_Helpers
|
|
3
3
|
|
|
4
|
+
let createQueuePolicyDocument = (~name, ~queueArn: string, ~lambdaArn: string) => {
|
|
5
|
+
let accountId = queueArn->accountIdOfQueueArn
|
|
6
|
+
PulumiAws.PolicyDocument.make(
|
|
7
|
+
~id=name ++ "QueuePolicy",
|
|
8
|
+
~statements=[
|
|
9
|
+
{
|
|
10
|
+
sid: "AllowLambdaToAccessQueue",
|
|
11
|
+
effect: Allow,
|
|
12
|
+
principal: Principals({
|
|
13
|
+
service: PrincipalId(AWS.Lambda.principal),
|
|
14
|
+
}),
|
|
15
|
+
actions: Actions(["sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes"]),
|
|
16
|
+
resources: Resource(queueArn),
|
|
17
|
+
conditions: {
|
|
18
|
+
arnEquals: [("AWS:SourceArn", ConditionValue(lambdaArn))]->Dict.fromArray,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
// ScheduledPublisher creates EventBridge rules at runtime
|
|
23
|
+
// (PutRule/PutTargets) targeting this queue directly, and EventBridge
|
|
24
|
+
// authorises SQS targets against the queue's resource policy — so the
|
|
25
|
+
// grant cannot name a single rule ARN. Scope it to rules in this
|
|
26
|
+
// account instead: SourceAccount closes the cross-account hole,
|
|
27
|
+
// ArnLike narrows the in-account surface to Events rules.
|
|
28
|
+
sid: "AllowCloudWatchEventsToSendToQueue",
|
|
29
|
+
effect: Allow,
|
|
30
|
+
principal: Principals({
|
|
31
|
+
service: PrincipalId(AWS.CloudwatchEventRule.principal),
|
|
32
|
+
}),
|
|
33
|
+
actions: Actions(["sqs:SendMessage"]),
|
|
34
|
+
resources: Resource(queueArn),
|
|
35
|
+
conditions: {
|
|
36
|
+
stringEquals: [("aws:SourceAccount", ConditionValue(accountId))]->Dict.fromArray,
|
|
37
|
+
arnLike: [
|
|
38
|
+
("aws:SourceArn", ConditionValue(`arn:aws:events:*:${accountId}:rule/*`)),
|
|
39
|
+
]->Dict.fromArray,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
)->toJsonString
|
|
44
|
+
}
|
|
45
|
+
|
|
4
46
|
let createQueuePolicy = (
|
|
5
47
|
queue: PulumiAws.SQS.Queue.t,
|
|
6
48
|
name,
|
|
7
49
|
lambda: Pulumi.Output.t<PulumiAws.Lambda.Function.t>,
|
|
8
50
|
opts,
|
|
9
51
|
) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
resources: Resource(queueArn),
|
|
30
|
-
conditions: {
|
|
31
|
-
arnEquals: [("AWS:SourceArn", ConditionValue(lambdaArn))]->Dict.fromArray,
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
sid: "AllowCloudWatchEventsToSendToQueue",
|
|
36
|
-
effect: Allow,
|
|
37
|
-
principal: Principals({
|
|
38
|
-
service: PrincipalId("events.amazonaws.com"),
|
|
39
|
-
}),
|
|
40
|
-
actions: Actions(["sqs:SendMessage"]),
|
|
41
|
-
resources: Resource(queueArn),
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
)
|
|
45
|
-
->toJsonString
|
|
46
|
-
->Pulumi.Input.make
|
|
47
|
-
let _queuePolicy = PulumiAws.SQS.QueuePolicy.make(
|
|
48
|
-
~name,
|
|
49
|
-
~args={queueUrl: queueId->Pulumi.Input.make, policy: queuePolicyDocument},
|
|
50
|
-
~opts=Some(opts),
|
|
51
|
-
)
|
|
52
|
-
})
|
|
52
|
+
// The document is a *value* derived from resolved ARNs, so it is built in an
|
|
53
|
+
// apply. The QueuePolicy itself stays outside: passing `queue.id` as an Output
|
|
54
|
+
// is what registers the policy -> queue dependency, without which Pulumi has
|
|
55
|
+
// no ordering constraint and can delete the queue first on a replacement,
|
|
56
|
+
// leaving the policy's delete polling a queue that no longer exists.
|
|
57
|
+
let queuePolicyDocument =
|
|
58
|
+
(queue.arn, lambda->Pulumi.Output.flatMap(lambda => lambda.arn))
|
|
59
|
+
->Pulumi.Output.all2
|
|
60
|
+
->Pulumi.Output.apply(((queueArn, lambdaArn)) =>
|
|
61
|
+
createQueuePolicyDocument(~name, ~queueArn, ~lambdaArn)
|
|
62
|
+
)
|
|
63
|
+
let _queuePolicy = PulumiAws.SQS.QueuePolicy.make(
|
|
64
|
+
~name,
|
|
65
|
+
~args={
|
|
66
|
+
queueUrl: queue.id->Pulumi.Output.asInput,
|
|
67
|
+
policy: queuePolicyDocument->Pulumi.Output.asInput,
|
|
68
|
+
},
|
|
69
|
+
~opts=Some(opts),
|
|
70
|
+
)
|
|
53
71
|
}
|
|
54
72
|
|
|
55
73
|
let createLambdaPolicy = (
|
|
@@ -100,10 +118,10 @@ let createLambdaPolicy = (
|
|
|
100
118
|
"dynamodb:UpdateItem",
|
|
101
119
|
"dynamodb:DeleteItem",
|
|
102
120
|
"dynamodb:BatchWriteItem",
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
121
|
+
// Cross-partition fences emit read-only `ConditionCheck`
|
|
122
|
+
// entries inside `TransactWriteItems` when a write
|
|
123
|
+
// doesn't touch the cross-partition tag's partition
|
|
124
|
+
// itself; this needs its own IAM action.
|
|
107
125
|
"dynamodb:ConditionCheckItem",
|
|
108
126
|
]),
|
|
109
127
|
resources: Resources(
|
|
@@ -193,4 +211,15 @@ let createLambdaPolicy = (
|
|
|
193
211
|
}
|
|
194
212
|
|
|
195
213
|
let subscribeLambda2SqsTopic = (~batchSize=?, lambda, name, queue, opts) =>
|
|
196
|
-
Util_EventSourceMapping.subscribeSqs(
|
|
214
|
+
Util_EventSourceMapping.subscribeSqs(
|
|
215
|
+
~lambda,
|
|
216
|
+
~name,
|
|
217
|
+
~queue,
|
|
218
|
+
~batchSize?,
|
|
219
|
+
~tags=AWS.Tags.make(
|
|
220
|
+
~name,
|
|
221
|
+
~kind=ReventlessCore.CommandTopic.componentType,
|
|
222
|
+
~role=EventSourceMapping,
|
|
223
|
+
),
|
|
224
|
+
~opts,
|
|
225
|
+
)
|
|
@@ -5,53 +5,67 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
|
5
5
|
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
6
6
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
7
|
import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
|
|
8
|
+
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
9
|
+
import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
|
|
8
10
|
import * as Adapter$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Adapter.res.mjs";
|
|
9
11
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
12
|
+
import * as CommandTopic$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic.res.mjs";
|
|
10
13
|
import * as Adapter_Helpers$ReventlessAws from "../Adapter_Helpers.res.mjs";
|
|
11
14
|
import * as Util_EventSourceMapping$ReventlessAws from "../../util/Util_EventSourceMapping.res.mjs";
|
|
12
15
|
|
|
16
|
+
function createQueuePolicyDocument(name, queueArn, lambdaArn) {
|
|
17
|
+
let accountId = Adapter_Helpers$ReventlessAws.accountIdOfQueueArn(queueArn);
|
|
18
|
+
return PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "QueuePolicy", [
|
|
19
|
+
{
|
|
20
|
+
Sid: "AllowLambdaToAccessQueue",
|
|
21
|
+
Principal: {
|
|
22
|
+
Service: AWS$ReventlessAws.Lambda.principal
|
|
23
|
+
},
|
|
24
|
+
Effect: "Allow",
|
|
25
|
+
Action: [
|
|
26
|
+
"sqs:ReceiveMessage",
|
|
27
|
+
"sqs:DeleteMessage",
|
|
28
|
+
"sqs:GetQueueAttributes"
|
|
29
|
+
],
|
|
30
|
+
Resource: queueArn,
|
|
31
|
+
Condition: {
|
|
32
|
+
ArnEquals: Object.fromEntries([[
|
|
33
|
+
"AWS:SourceArn",
|
|
34
|
+
lambdaArn
|
|
35
|
+
]])
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
Sid: "AllowCloudWatchEventsToSendToQueue",
|
|
40
|
+
Principal: {
|
|
41
|
+
Service: AWS$ReventlessAws.CloudwatchEventRule.principal
|
|
42
|
+
},
|
|
43
|
+
Effect: "Allow",
|
|
44
|
+
Action: ["sqs:SendMessage"],
|
|
45
|
+
Resource: queueArn,
|
|
46
|
+
Condition: {
|
|
47
|
+
StringEquals: Object.fromEntries([[
|
|
48
|
+
"aws:SourceAccount",
|
|
49
|
+
accountId
|
|
50
|
+
]]),
|
|
51
|
+
ArnLike: Object.fromEntries([[
|
|
52
|
+
"aws:SourceArn",
|
|
53
|
+
`arn:aws:events:*:` + accountId + `:rule/*`
|
|
54
|
+
]])
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
]));
|
|
58
|
+
}
|
|
59
|
+
|
|
13
60
|
function createQueuePolicy(queue, name, lambda, opts) {
|
|
14
|
-
Pulumi.all([
|
|
61
|
+
let queuePolicyDocument = Pulumi.all([
|
|
15
62
|
queue.arn,
|
|
16
|
-
queue.id,
|
|
17
63
|
Output$Pulumi.flatMap(lambda, lambda => lambda.arn)
|
|
18
|
-
]).apply(param =>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Principal: {
|
|
24
|
-
Service: "lambda.amazonaws.com"
|
|
25
|
-
},
|
|
26
|
-
Effect: "Allow",
|
|
27
|
-
Action: [
|
|
28
|
-
"sqs:ReceiveMessage",
|
|
29
|
-
"sqs:DeleteMessage",
|
|
30
|
-
"sqs:GetQueueAttributes"
|
|
31
|
-
],
|
|
32
|
-
Resource: queueArn,
|
|
33
|
-
Condition: {
|
|
34
|
-
ArnEquals: Object.fromEntries([[
|
|
35
|
-
"AWS:SourceArn",
|
|
36
|
-
param[2]
|
|
37
|
-
]])
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
Sid: "AllowCloudWatchEventsToSendToQueue",
|
|
42
|
-
Principal: {
|
|
43
|
-
Service: "events.amazonaws.com"
|
|
44
|
-
},
|
|
45
|
-
Effect: "Allow",
|
|
46
|
-
Action: ["sqs:SendMessage"],
|
|
47
|
-
Resource: queueArn
|
|
48
|
-
}
|
|
49
|
-
]));
|
|
50
|
-
new (Aws.sqs.QueuePolicy)(name, {
|
|
51
|
-
policy: queuePolicyDocument,
|
|
52
|
-
queueUrl: param[1]
|
|
53
|
-
}, opts);
|
|
54
|
-
});
|
|
64
|
+
]).apply(param => createQueuePolicyDocument(name, param[0], param[1]));
|
|
65
|
+
new (Aws.sqs.QueuePolicy)(name, {
|
|
66
|
+
policy: queuePolicyDocument,
|
|
67
|
+
queueUrl: queue.id
|
|
68
|
+
}, opts);
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
function createLambdaPolicy(lambdaRole, name, queue, resources, opts) {
|
|
@@ -134,10 +148,11 @@ function createLambdaPolicy(lambdaRole, name, queue, resources, opts) {
|
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
function subscribeLambda2SqsTopic(batchSize, lambda, name, queue, opts) {
|
|
137
|
-
return Util_EventSourceMapping$ReventlessAws.subscribeSqs(lambda, name, queue, batchSize, opts);
|
|
151
|
+
return Util_EventSourceMapping$ReventlessAws.subscribeSqs(lambda, name, queue, batchSize, AWS_Tags$ReventlessAws.make(name, CommandTopic$ReventlessCore.componentType, "EventSourceMapping", undefined, undefined, undefined, undefined, undefined), opts);
|
|
138
152
|
}
|
|
139
153
|
|
|
140
154
|
export {
|
|
155
|
+
createQueuePolicyDocument,
|
|
141
156
|
createQueuePolicy,
|
|
142
157
|
createLambdaPolicy,
|
|
143
158
|
subscribeLambda2SqsTopic,
|
|
@@ -77,6 +77,12 @@ let make: ReventlessCore.Counter_Adapter.handlerMaker = (
|
|
|
77
77
|
~targetName=name,
|
|
78
78
|
~sourceName,
|
|
79
79
|
~source,
|
|
80
|
+
~tags=AWS.Tags.make(
|
|
81
|
+
~name=sourceName->ReventlessCore.Util.baseName ++ ("2" ++ name),
|
|
82
|
+
~kind=ReventlessCore.ComponentType.Counter,
|
|
83
|
+
~role=EventSourceMapping,
|
|
84
|
+
~component=name,
|
|
85
|
+
),
|
|
80
86
|
~opts,
|
|
81
87
|
)
|
|
82
88
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
4
|
import * as Pulumi from "@pulumi/pulumi";
|
|
5
|
+
import * as Util$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util.res.mjs";
|
|
6
|
+
import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
|
|
5
7
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
6
8
|
import * as Util_DynamoDbStream$ReventlessAws from "../../util/Util_DynamoDbStream.res.mjs";
|
|
7
9
|
import * as Util_EventSourceMapping$ReventlessAws from "../../util/Util_EventSourceMapping.res.mjs";
|
|
@@ -42,7 +44,7 @@ function make(name, referencesName, referencesDb, countsName, countsDb, param, s
|
|
|
42
44
|
_0: "Counter"
|
|
43
45
|
}, "Counter", match.code, match.sourceCodeHash, envVars, 1024, 30, undefined, undefined, undefined, undefined, undefined, componentOpts);
|
|
44
46
|
let lambda = runtime.parts.lambda;
|
|
45
|
-
let subscribe = (sourceName, source) => Util_EventSourceMapping$ReventlessAws.subscribe(undefined, lambda, name, sourceName, source, opts);
|
|
47
|
+
let subscribe = (sourceName, source) => Util_EventSourceMapping$ReventlessAws.subscribe(undefined, lambda, name, sourceName, source, AWS_Tags$ReventlessAws.make(Util$ReventlessCore.baseName(sourceName) + ("2" + name), "Counter", "EventSourceMapping", undefined, name, undefined, undefined, undefined), opts);
|
|
46
48
|
subscribe(referencesName, referencesStream);
|
|
47
49
|
subscribe(countsName, countsStream);
|
|
48
50
|
return {
|
|
@@ -118,11 +118,19 @@ let toItem = (
|
|
|
118
118
|
->ReventlessCore.Message.decomposeMeta
|
|
119
119
|
->Array.forEach(((k, v)) => item->Dict.set(k, v))
|
|
120
120
|
|
|
121
|
-
// Add individual tag attributes for GSI queries
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
// Add individual tag attributes for GSI queries. An empty tag value is a
|
|
122
|
+
// legitimate model state (a composite-partition member that is absent at this
|
|
123
|
+
// level of the hierarchy), but `tag_<key>` is a GSI hash key and DynamoDB
|
|
124
|
+
// rejects an empty string for a key attribute — the whole append fails. Skip
|
|
125
|
+
// the attribute instead: the index goes sparse (its intended mechanism), the
|
|
126
|
+
// event still records the tag in its `tags` payload, and `tag_composite`, the
|
|
127
|
+
// partition key and the OCC fences are all unaffected.
|
|
128
|
+
event.tags->Array.forEach(tag =>
|
|
129
|
+
if tag.value != "" {
|
|
130
|
+
let attrName = tagToAttributeName(tag.key)
|
|
131
|
+
item->Dict.set(attrName, tag.value->JSON.Encode.string)
|
|
132
|
+
}
|
|
133
|
+
)
|
|
126
134
|
|
|
127
135
|
// Add composite tag attribute if multiple tags. The tag set here is exactly the
|
|
128
136
|
// event's *entity* tags (no framework provenance tag is smuggled in — see
|
|
@@ -370,7 +378,7 @@ let resolveBaseItem = (table: resolvedTable, keyItem: JSON.t): Effect.t<
|
|
|
370
378
|
}
|
|
371
379
|
}
|
|
372
380
|
|
|
373
|
-
let
|
|
381
|
+
let queryBySingleTagCrossPartitionStreamIndexed = (
|
|
374
382
|
table: resolvedTable,
|
|
375
383
|
tag: Reventless.DcbTag.tag,
|
|
376
384
|
~after: option<string>=?,
|
|
@@ -419,6 +427,21 @@ let queryBySingleTagCrossPartitionStream = (
|
|
|
419
427
|
)
|
|
420
428
|
}
|
|
421
429
|
|
|
430
|
+
let queryBySingleTagCrossPartitionStream = (
|
|
431
|
+
table: resolvedTable,
|
|
432
|
+
tag: Reventless.DcbTag.tag,
|
|
433
|
+
~after: option<string>=?,
|
|
434
|
+
) =>
|
|
435
|
+
if tag.value == "" {
|
|
436
|
+
// `toItem` does not index an empty tag value (a GSI hash key cannot hold an
|
|
437
|
+
// empty string), so nothing carries it in this index. Querying for it would
|
|
438
|
+
// be rejected outright too — a key condition value cannot be empty either.
|
|
439
|
+
// The empty result is the honest answer; an exception is not.
|
|
440
|
+
Stream.empty
|
|
441
|
+
} else {
|
|
442
|
+
queryBySingleTagCrossPartitionStreamIndexed(table, tag, ~after?)
|
|
443
|
+
}
|
|
444
|
+
|
|
422
445
|
// --- Query Item Execution ---
|
|
423
446
|
|
|
424
447
|
let executeQueryItem = async (
|
|
@@ -103,6 +103,9 @@ function toItem(position, event, partitionTag, recordedAt) {
|
|
|
103
103
|
item[param[0]] = param[1];
|
|
104
104
|
});
|
|
105
105
|
event.tags.forEach(tag => {
|
|
106
|
+
if (tag.value === "") {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
106
109
|
let attrName = tagToAttributeName(tag.key);
|
|
107
110
|
item[attrName] = tag.value;
|
|
108
111
|
});
|
|
@@ -281,7 +284,7 @@ function resolveBaseItem(table, keyItem) {
|
|
|
281
284
|
}))), DynamoDb_Error$ReventlessAws.retrySchedule), err => Effect$1.fail(DynamoDb_Error$ReventlessAws.message(err))), result => result.Item);
|
|
282
285
|
}
|
|
283
286
|
|
|
284
|
-
function
|
|
287
|
+
function queryBySingleTagCrossPartitionStreamIndexed(table, tag, after) {
|
|
285
288
|
let indexName = tagToAttributeName(tag.key);
|
|
286
289
|
let expressionAttributeValues = Object.fromEntries([[
|
|
287
290
|
":val",
|
|
@@ -326,6 +329,14 @@ function queryBySingleTagCrossPartitionStream(table, tag, after) {
|
|
|
326
329
|
});
|
|
327
330
|
}
|
|
328
331
|
|
|
332
|
+
function queryBySingleTagCrossPartitionStream(table, tag, after) {
|
|
333
|
+
if (tag.value === "") {
|
|
334
|
+
return Stream$1.empty;
|
|
335
|
+
} else {
|
|
336
|
+
return queryBySingleTagCrossPartitionStreamIndexed(table, tag, after);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
329
340
|
async function executeQueryItem(table, queryItem, after, crossPartitionTagKeysOpt) {
|
|
330
341
|
let crossPartitionTagKeys = crossPartitionTagKeysOpt !== undefined ? crossPartitionTagKeysOpt : [];
|
|
331
342
|
let tags = queryItem.tags;
|
|
@@ -1164,6 +1175,7 @@ export {
|
|
|
1164
1175
|
buildQueryByPartitionKeyInput,
|
|
1165
1176
|
queryByPartitionKey,
|
|
1166
1177
|
resolveBaseItem,
|
|
1178
|
+
queryBySingleTagCrossPartitionStreamIndexed,
|
|
1167
1179
|
queryBySingleTagCrossPartitionStream,
|
|
1168
1180
|
executeQueryItem,
|
|
1169
1181
|
deduplicateByPosition,
|