From 2dea83565214fbceb6d8619f339ec249dcfbeae7 Mon Sep 17 00:00:00 2001
From: Marcio Cruz de Almeida <67694075+marciocadev@users.noreply.github.com>
Date: Sat, 20 Apr 2024 15:56:54 -0300
Subject: [PATCH] feat(sdk): dead letter queue support for queues (#6060)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
⛔ As the dead-letter queue linked to the function is a specific case of AWS, I will remove it from the scope of this PR.
- [x] dlq (with retries) for queue (tf-aws / awscdk)
- [x] dlq (with retries) for queue (sim)
~dlq for function (tf-aws / awscdk)~
~dlq for function (sim)~
Closes #6033
## Checklist
- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [ ] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing
*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
---
docs/docs/04-standard-library/cloud/queue.md | 76 +++++
docs/docs/04-standard-library/cloud/topic.md | 14 +
.../sdk_tests/queue/dead-letter-queue.test.w | 63 ++++
libs/awscdk/src/queue.ts | 20 +-
.../test/__snapshots__/queue.test.ts.snap | 3 +
.../incomplete_inflight_namespace.snap | 12 +-
.../completions/namespace_middle_dot.snap | 12 +-
.../completions/new_expression_nested.snap | 2 +-
.../partial_type_reference_annotation.snap | 12 +-
.../variable_type_annotation_namespace.snap | 12 +-
libs/wingsdk/src/cloud/queue.md | 16 +
libs/wingsdk/src/cloud/queue.ts | 27 ++
.../shared-aws/queue.setconsumer.inflight.ts | 12 +-
libs/wingsdk/src/target-sim/queue.inflight.ts | 60 +++-
.../target-sim/queue.setconsumer.inflight.ts | 11 +-
libs/wingsdk/src/target-sim/queue.ts | 21 ++
.../src/target-sim/schema-resources.ts | 9 +
libs/wingsdk/src/target-tf-aws/queue.ts | 37 ++-
.../__snapshots__/captures.test.ts.snap | 3 +
.../__snapshots__/queue.test.ts.snap | 3 +
tools/hangar/__snapshots__/platform.ts.snap | 18 +
...dead-letter-queue.test.w_compile_tf-aws.md | 307 ++++++++++++++++++
.../dead-letter-queue.test.w_test_sim.md | 13 +
.../queue/queue-ref.test.w_compile_tf-aws.md | 5 +-
.../set_consumer.test.w_compile_tf-aws.md | 10 +-
.../subscribe-queue.test.w_compile_tf-aws.md | 10 +-
.../valid/captures.test.w_compile_tf-aws.md | 5 +-
.../file_counter.test.w_compile_tf-aws.md | 5 +-
.../valid/hello.test.w_compile_tf-aws.md | 5 +-
...light-subscribers.test.w_compile_tf-aws.md | 5 +-
.../valid/redis.test.w_compile_tf-aws.md | 5 +-
.../valid/resource.test.w_compile_tf-aws.md | 5 +-
.../while_loop_await.test.w_compile_tf-aws.md | 5 +-
33 files changed, 777 insertions(+), 46 deletions(-)
create mode 100644 examples/tests/sdk_tests/queue/dead-letter-queue.test.w
create mode 100644 tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/dead-letter-queue.test.w_compile_tf-aws.md
create mode 100644 tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/dead-letter-queue.test.w_test_sim.md
diff --git a/docs/docs/04-standard-library/cloud/queue.md b/docs/docs/04-standard-library/cloud/queue.md
index 646d6f42184..2e9101782ea 100644
--- a/docs/docs/04-standard-library/cloud/queue.md
+++ b/docs/docs/04-standard-library/cloud/queue.md
@@ -62,6 +62,22 @@ new cloud.Function(inflight () => {
});
```
+### Adding a dead-letter queue
+
+Creating a queue and adding a dead-letter queue with the maximum number of attempts configured
+
+```ts playground
+bring cloud;
+
+let dlq = new cloud.Queue() as "dead-letter queue";
+let q = new cloud.Queue(
+ dlq: {
+ queue: dlq,
+ maxDeliveryAttempts: 2
+ }
+);
+```
+
### Referencing an external queue
If you would like to reference an existing queue from within your application you can use the
@@ -270,6 +286,52 @@ The tree node.
## Structs
+### DeadLetterQueueProps
+
+Dead letter queue options.
+
+#### Initializer
+
+```wing
+bring cloud;
+
+let DeadLetterQueueProps = cloud.DeadLetterQueueProps{ ... };
+```
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| queue
| Queue
| Queue to receive messages that failed processing. |
+| maxDeliveryAttempts
| num
| Number of times a message will be processed before being sent to the dead-letter queue. |
+
+---
+
+##### `queue`Required
+
+```wing
+queue: Queue;
+```
+
+- *Type:* Queue
+
+Queue to receive messages that failed processing.
+
+---
+
+##### `maxDeliveryAttempts`Optional
+
+```wing
+maxDeliveryAttempts: num;
+```
+
+- *Type:* num
+- *Default:* 1
+
+Number of times a message will be processed before being sent to the dead-letter queue.
+
+---
+
### QueueProps
Options for `Queue`.
@@ -286,11 +348,25 @@ let QueueProps = cloud.QueueProps{ ... };
| **Name** | **Type** | **Description** |
| --- | --- | --- |
+| dlq
| DeadLetterQueueProps
| A dead-letter queue. |
| retentionPeriod
| duration
| How long a queue retains a message. |
| timeout
| duration
| How long a queue's consumers have to process a message. |
---
+##### `dlq`Optional
+
+```wing
+dlq: DeadLetterQueueProps;
+```
+
+- *Type:* DeadLetterQueueProps
+- *Default:* no dead letter queue
+
+A dead-letter queue.
+
+---
+
##### `retentionPeriod`Optional
```wing
diff --git a/docs/docs/04-standard-library/cloud/topic.md b/docs/docs/04-standard-library/cloud/topic.md
index 3881a60741e..f07374488a7 100644
--- a/docs/docs/04-standard-library/cloud/topic.md
+++ b/docs/docs/04-standard-library/cloud/topic.md
@@ -395,11 +395,25 @@ let TopicSubscribeQueueOptions = cloud.TopicSubscribeQueueOptions{ ... };
| **Name** | **Type** | **Description** |
| --- | --- | --- |
+| dlq
| DeadLetterQueueProps
| A dead-letter queue. |
| retentionPeriod
| duration
| How long a queue retains a message. |
| timeout
| duration
| How long a queue's consumers have to process a message. |
---
+##### `dlq`Optional
+
+```wing
+dlq: DeadLetterQueueProps;
+```
+
+- *Type:* DeadLetterQueueProps
+- *Default:* no dead letter queue
+
+A dead-letter queue.
+
+---
+
##### `retentionPeriod`Optional
```wing
diff --git a/examples/tests/sdk_tests/queue/dead-letter-queue.test.w b/examples/tests/sdk_tests/queue/dead-letter-queue.test.w
new file mode 100644
index 00000000000..a75c0587426
--- /dev/null
+++ b/examples/tests/sdk_tests/queue/dead-letter-queue.test.w
@@ -0,0 +1,63 @@
+bring cloud;
+bring util;
+
+let counter_received_messages = new cloud.Counter();
+
+let dlq_without_retries = new cloud.Queue() as "dlq without retries";
+let queue_without_retries = new cloud.Queue(
+ dlq: { queue: dlq_without_retries }
+) as "queue without retries";
+queue_without_retries.setConsumer(inflight (msg: str) => {
+ counter_received_messages.inc(1, msg);
+ if msg == "fail" {
+ throw "error";
+ }
+});
+
+
+new std.Test(
+ inflight () => {
+ queue_without_retries.push("Hello");
+ queue_without_retries.push("fail");
+ queue_without_retries.push("World!");
+
+ // wait until it executes once.
+ assert(util.waitUntil(inflight () => { return counter_received_messages.peek("Hello") == 1; }));
+ assert(util.waitUntil(inflight () => { return counter_received_messages.peek("World!") == 1; }));
+ assert(util.waitUntil(inflight () => { return counter_received_messages.peek("fail") == 1; }));
+
+ // check if the "fail" message has arrived at the dead-letter queue
+ assert(util.waitUntil(inflight () => { return dlq_without_retries.pop() == "fail"; }));
+ },
+ // To make this test work on AWS, it's necessary to set a high timeout
+ // because if the message fails, we have to wait for the visibility timeout
+ // to expire in order to retrieve the same message from the queue again.
+ timeout: 5m) as "one execution and send fail message to dead-letter queue";
+
+let dlq_with_retries = new cloud.Queue() as "dlq with retries";
+let queue_with_retries = new cloud.Queue(
+ dlq: {
+ queue: dlq_with_retries,
+ maxDeliveryAttempts: 2
+ }
+) as "queue with retries";
+queue_with_retries.setConsumer(inflight (msg: str) => {
+ counter_received_messages.inc(1, msg);
+ if msg == "fail" {
+ throw "error";
+ }
+});
+
+new std.Test(inflight () => {
+ queue_with_retries.push("Hello");
+ queue_with_retries.push("fail");
+ queue_with_retries.push("World!");
+
+ // wait until it executes once and retry one more times.
+ assert(util.waitUntil(inflight () => { return counter_received_messages.peek("Hello") == 1; }));
+ assert(util.waitUntil(inflight () => { return counter_received_messages.peek("World!") == 1; }));
+ assert(util.waitUntil(inflight () => { return counter_received_messages.peek("fail") == 2; }));
+
+ // check if the "fail" message has arrived at the dead-letter queue
+ assert(util.waitUntil(inflight () => { return dlq_with_retries.pop() == "fail"; }));
+}, timeout: 5m) as "one execution, two retries and send the fail message to dead-letter queue";
diff --git a/libs/awscdk/src/queue.ts b/libs/awscdk/src/queue.ts
index 248fe7b3ca2..4ea5a0887fe 100644
--- a/libs/awscdk/src/queue.ts
+++ b/libs/awscdk/src/queue.ts
@@ -7,7 +7,7 @@ import { App } from "./app";
import { std, core, cloud } from "@winglang/sdk";
import { convertBetweenHandlers } from "@winglang/sdk/lib/shared/convert";
import { calculateQueuePermissions } from "@winglang/sdk/lib/shared-aws/permissions";
-import { IAwsQueue } from "@winglang/sdk/lib/shared-aws/queue";
+import { IAwsQueue, Queue as AwsQueue } from "@winglang/sdk/lib/shared-aws/queue";
import { addPolicyStatements, isAwsCdkFunction } from "./function";
/**
@@ -23,14 +23,27 @@ export class Queue extends cloud.Queue implements IAwsQueue {
super(scope, id, props);
this.timeout = props.timeout ?? std.Duration.fromSeconds(30);
- this.queue = new SQSQueue(this, "Default", {
+ const queueOpt = props.dlq ? {
visibilityTimeout: props.timeout
? Duration.seconds(props.timeout?.seconds)
: Duration.seconds(30),
retentionPeriod: props.retentionPeriod
? Duration.seconds(props.retentionPeriod?.seconds)
: Duration.hours(1),
- });
+ deadLetterQueue: {
+ queue: SQSQueue.fromQueueArn(this, "DeadLetterQueue", AwsQueue.from(props.dlq.queue)?.queueArn!),
+ maxReceiveCount: props.dlq.maxDeliveryAttempts ?? cloud.DEFAULT_DELIVERY_ATTEMPTS,
+ }
+ } : {
+ visibilityTimeout: props.timeout
+ ? Duration.seconds(props.timeout?.seconds)
+ : Duration.seconds(30),
+ retentionPeriod: props.retentionPeriod
+ ? Duration.seconds(props.retentionPeriod?.seconds)
+ : Duration.hours(1),
+ }
+
+ this.queue = new SQSQueue(this, "Default", queueOpt);
}
public setConsumer(
@@ -63,6 +76,7 @@ export class Queue extends cloud.Queue implements IAwsQueue {
const eventSource = new SqsEventSource(this.queue, {
batchSize: props.batchSize ?? 1,
+ reportBatchItemFailures: true,
});
fn.awscdkFunction.addEventSource(eventSource);
diff --git a/libs/awscdk/test/__snapshots__/queue.test.ts.snap b/libs/awscdk/test/__snapshots__/queue.test.ts.snap
index fce461496c1..ff7d8c6f540 100644
--- a/libs/awscdk/test/__snapshots__/queue.test.ts.snap
+++ b/libs/awscdk/test/__snapshots__/queue.test.ts.snap
@@ -192,6 +192,9 @@ exports[`queue with a consumer function 2`] = `
"FunctionName": {
"Ref": "QueueSetConsumer06749388A",
},
+ "FunctionResponseTypes": [
+ "ReportBatchItemFailures",
+ ],
},
"Type": "AWS::Lambda::EventSourceMapping",
},
diff --git a/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap b/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap
index 7a230715cf6..1fd2aa61a8f 100644
--- a/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap
+++ b/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap
@@ -47,7 +47,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 7
documentation:
kind: markdown
- value: "```wing\nclass Queue\n```\n---\nA queue.\n\n### Initializer\n- `...props` — `QueueProps?`\n \n - `retentionPeriod?` — `duration?` — How long a queue retains a message.\n - `timeout?` — `duration?` — How long a queue's consumers have to process a message.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `approxSize` — `inflight (): num` — Retrieve the approximate number of messages in the queue.\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `pop` — `inflight (): str?` — Pop a message from the queue.\n- `purge` — `inflight (): void` — Purge all of the messages in the queue.\n- `push` — `inflight (...messages: Array?): void` — Push one or more messages to the queue.\n- `setConsumer` — `preflight (handler: inflight (message: str): void, props: QueueSetConsumerOptions?): Function` — Create a function to consume messages from this queue.\n- `toString` — `preflight (): str` — Returns a string representation of this construct."
+ value: "```wing\nclass Queue\n```\n---\nA queue.\n\n### Initializer\n- `...props` — `QueueProps?`\n \n - `dlq?` — `DeadLetterQueueProps?` — A dead-letter queue.\n - `retentionPeriod?` — `duration?` — How long a queue retains a message.\n - `timeout?` — `duration?` — How long a queue's consumers have to process a message.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `approxSize` — `inflight (): num` — Retrieve the approximate number of messages in the queue.\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `pop` — `inflight (): str?` — Pop a message from the queue.\n- `purge` — `inflight (): void` — Purge all of the messages in the queue.\n- `push` — `inflight (...messages: Array?): void` — Push one or more messages to the queue.\n- `setConsumer` — `preflight (handler: inflight (message: str): void, props: QueueSetConsumerOptions?): Function` — Create a function to consume messages from this queue.\n- `toString` — `preflight (): str` — Returns a string representation of this construct."
sortText: gg|Queue
- label: Schedule
kind: 7
@@ -229,6 +229,12 @@ source: libs/wingc/src/lsp/completions.rs
kind: markdown
value: "```wing\nstruct CounterProps\n```\n---\nOptions for `Counter`.\n### Fields\n- `initial?` — `num?` — The initial value of the counter."
sortText: hh|CounterProps
+- label: DeadLetterQueueProps
+ kind: 22
+ documentation:
+ kind: markdown
+ value: "```wing\nstruct DeadLetterQueueProps\n```\n---\nDead letter queue options.\n### Fields\n- `queue` — `Queue` — Queue to receive messages that failed processing.\n- `maxDeliveryAttempts?` — `num?` — Number of times a message will be processed before being sent to the dead-letter queue."
+ sortText: hh|DeadLetterQueueProps
- label: DomainProps
kind: 22
documentation:
@@ -269,7 +275,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 22
documentation:
kind: markdown
- value: "```wing\nstruct QueueProps\n```\n---\nOptions for `Queue`.\n### Fields\n- `retentionPeriod?` — `duration?` — How long a queue retains a message.\n- `timeout?` — `duration?` — How long a queue's consumers have to process a message."
+ value: "```wing\nstruct QueueProps\n```\n---\nOptions for `Queue`.\n### Fields\n- `dlq?` — `DeadLetterQueueProps?` — A dead-letter queue.\n- `retentionPeriod?` — `duration?` — How long a queue retains a message.\n- `timeout?` — `duration?` — How long a queue's consumers have to process a message."
sortText: hh|QueueProps
- label: QueueSetConsumerOptions
kind: 22
@@ -323,7 +329,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 22
documentation:
kind: markdown
- value: "```wing\nstruct TopicSubscribeQueueOptions extends QueueProps\n```\n---\nOptions for `Topic.subscribeQueue`.\n### Fields\n- `retentionPeriod?` — `duration?`\n- `timeout?` — `duration?`"
+ value: "```wing\nstruct TopicSubscribeQueueOptions extends QueueProps\n```\n---\nOptions for `Topic.subscribeQueue`.\n### Fields\n- `dlq?` — `DeadLetterQueueProps?`\n- `retentionPeriod?` — `duration?`\n- `timeout?` — `duration?`"
sortText: hh|TopicSubscribeQueueOptions
- label: WebsiteDomainOptions
kind: 22
diff --git a/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap b/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap
index 7a230715cf6..1fd2aa61a8f 100644
--- a/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap
+++ b/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap
@@ -47,7 +47,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 7
documentation:
kind: markdown
- value: "```wing\nclass Queue\n```\n---\nA queue.\n\n### Initializer\n- `...props` — `QueueProps?`\n \n - `retentionPeriod?` — `duration?` — How long a queue retains a message.\n - `timeout?` — `duration?` — How long a queue's consumers have to process a message.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `approxSize` — `inflight (): num` — Retrieve the approximate number of messages in the queue.\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `pop` — `inflight (): str?` — Pop a message from the queue.\n- `purge` — `inflight (): void` — Purge all of the messages in the queue.\n- `push` — `inflight (...messages: Array?): void` — Push one or more messages to the queue.\n- `setConsumer` — `preflight (handler: inflight (message: str): void, props: QueueSetConsumerOptions?): Function` — Create a function to consume messages from this queue.\n- `toString` — `preflight (): str` — Returns a string representation of this construct."
+ value: "```wing\nclass Queue\n```\n---\nA queue.\n\n### Initializer\n- `...props` — `QueueProps?`\n \n - `dlq?` — `DeadLetterQueueProps?` — A dead-letter queue.\n - `retentionPeriod?` — `duration?` — How long a queue retains a message.\n - `timeout?` — `duration?` — How long a queue's consumers have to process a message.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `approxSize` — `inflight (): num` — Retrieve the approximate number of messages in the queue.\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `pop` — `inflight (): str?` — Pop a message from the queue.\n- `purge` — `inflight (): void` — Purge all of the messages in the queue.\n- `push` — `inflight (...messages: Array?): void` — Push one or more messages to the queue.\n- `setConsumer` — `preflight (handler: inflight (message: str): void, props: QueueSetConsumerOptions?): Function` — Create a function to consume messages from this queue.\n- `toString` — `preflight (): str` — Returns a string representation of this construct."
sortText: gg|Queue
- label: Schedule
kind: 7
@@ -229,6 +229,12 @@ source: libs/wingc/src/lsp/completions.rs
kind: markdown
value: "```wing\nstruct CounterProps\n```\n---\nOptions for `Counter`.\n### Fields\n- `initial?` — `num?` — The initial value of the counter."
sortText: hh|CounterProps
+- label: DeadLetterQueueProps
+ kind: 22
+ documentation:
+ kind: markdown
+ value: "```wing\nstruct DeadLetterQueueProps\n```\n---\nDead letter queue options.\n### Fields\n- `queue` — `Queue` — Queue to receive messages that failed processing.\n- `maxDeliveryAttempts?` — `num?` — Number of times a message will be processed before being sent to the dead-letter queue."
+ sortText: hh|DeadLetterQueueProps
- label: DomainProps
kind: 22
documentation:
@@ -269,7 +275,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 22
documentation:
kind: markdown
- value: "```wing\nstruct QueueProps\n```\n---\nOptions for `Queue`.\n### Fields\n- `retentionPeriod?` — `duration?` — How long a queue retains a message.\n- `timeout?` — `duration?` — How long a queue's consumers have to process a message."
+ value: "```wing\nstruct QueueProps\n```\n---\nOptions for `Queue`.\n### Fields\n- `dlq?` — `DeadLetterQueueProps?` — A dead-letter queue.\n- `retentionPeriod?` — `duration?` — How long a queue retains a message.\n- `timeout?` — `duration?` — How long a queue's consumers have to process a message."
sortText: hh|QueueProps
- label: QueueSetConsumerOptions
kind: 22
@@ -323,7 +329,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 22
documentation:
kind: markdown
- value: "```wing\nstruct TopicSubscribeQueueOptions extends QueueProps\n```\n---\nOptions for `Topic.subscribeQueue`.\n### Fields\n- `retentionPeriod?` — `duration?`\n- `timeout?` — `duration?`"
+ value: "```wing\nstruct TopicSubscribeQueueOptions extends QueueProps\n```\n---\nOptions for `Topic.subscribeQueue`.\n### Fields\n- `dlq?` — `DeadLetterQueueProps?`\n- `retentionPeriod?` — `duration?`\n- `timeout?` — `duration?`"
sortText: hh|TopicSubscribeQueueOptions
- label: WebsiteDomainOptions
kind: 22
diff --git a/libs/wingc/src/lsp/snapshots/completions/new_expression_nested.snap b/libs/wingc/src/lsp/snapshots/completions/new_expression_nested.snap
index 396726602d3..bfe3e0f0bf6 100644
--- a/libs/wingc/src/lsp/snapshots/completions/new_expression_nested.snap
+++ b/libs/wingc/src/lsp/snapshots/completions/new_expression_nested.snap
@@ -82,7 +82,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 7
documentation:
kind: markdown
- value: "```wing\nclass Queue\n```\n---\nA queue.\n\n### Initializer\n- `...props` — `QueueProps?`\n \n - `retentionPeriod?` — `duration?` — How long a queue retains a message.\n - `timeout?` — `duration?` — How long a queue's consumers have to process a message.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `approxSize` — `inflight (): num` — Retrieve the approximate number of messages in the queue.\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `pop` — `inflight (): str?` — Pop a message from the queue.\n- `purge` — `inflight (): void` — Purge all of the messages in the queue.\n- `push` — `inflight (...messages: Array?): void` — Push one or more messages to the queue.\n- `setConsumer` — `preflight (handler: inflight (message: str): void, props: QueueSetConsumerOptions?): Function` — Create a function to consume messages from this queue.\n- `toString` — `preflight (): str` — Returns a string representation of this construct."
+ value: "```wing\nclass Queue\n```\n---\nA queue.\n\n### Initializer\n- `...props` — `QueueProps?`\n \n - `dlq?` — `DeadLetterQueueProps?` — A dead-letter queue.\n - `retentionPeriod?` — `duration?` — How long a queue retains a message.\n - `timeout?` — `duration?` — How long a queue's consumers have to process a message.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `approxSize` — `inflight (): num` — Retrieve the approximate number of messages in the queue.\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `pop` — `inflight (): str?` — Pop a message from the queue.\n- `purge` — `inflight (): void` — Purge all of the messages in the queue.\n- `push` — `inflight (...messages: Array?): void` — Push one or more messages to the queue.\n- `setConsumer` — `preflight (handler: inflight (message: str): void, props: QueueSetConsumerOptions?): Function` — Create a function to consume messages from this queue.\n- `toString` — `preflight (): str` — Returns a string representation of this construct."
sortText: gg|Queue
insertText: Queue($1)
insertTextFormat: 2
diff --git a/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap b/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap
index 7a230715cf6..1fd2aa61a8f 100644
--- a/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap
+++ b/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap
@@ -47,7 +47,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 7
documentation:
kind: markdown
- value: "```wing\nclass Queue\n```\n---\nA queue.\n\n### Initializer\n- `...props` — `QueueProps?`\n \n - `retentionPeriod?` — `duration?` — How long a queue retains a message.\n - `timeout?` — `duration?` — How long a queue's consumers have to process a message.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `approxSize` — `inflight (): num` — Retrieve the approximate number of messages in the queue.\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `pop` — `inflight (): str?` — Pop a message from the queue.\n- `purge` — `inflight (): void` — Purge all of the messages in the queue.\n- `push` — `inflight (...messages: Array?): void` — Push one or more messages to the queue.\n- `setConsumer` — `preflight (handler: inflight (message: str): void, props: QueueSetConsumerOptions?): Function` — Create a function to consume messages from this queue.\n- `toString` — `preflight (): str` — Returns a string representation of this construct."
+ value: "```wing\nclass Queue\n```\n---\nA queue.\n\n### Initializer\n- `...props` — `QueueProps?`\n \n - `dlq?` — `DeadLetterQueueProps?` — A dead-letter queue.\n - `retentionPeriod?` — `duration?` — How long a queue retains a message.\n - `timeout?` — `duration?` — How long a queue's consumers have to process a message.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `approxSize` — `inflight (): num` — Retrieve the approximate number of messages in the queue.\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `pop` — `inflight (): str?` — Pop a message from the queue.\n- `purge` — `inflight (): void` — Purge all of the messages in the queue.\n- `push` — `inflight (...messages: Array?): void` — Push one or more messages to the queue.\n- `setConsumer` — `preflight (handler: inflight (message: str): void, props: QueueSetConsumerOptions?): Function` — Create a function to consume messages from this queue.\n- `toString` — `preflight (): str` — Returns a string representation of this construct."
sortText: gg|Queue
- label: Schedule
kind: 7
@@ -229,6 +229,12 @@ source: libs/wingc/src/lsp/completions.rs
kind: markdown
value: "```wing\nstruct CounterProps\n```\n---\nOptions for `Counter`.\n### Fields\n- `initial?` — `num?` — The initial value of the counter."
sortText: hh|CounterProps
+- label: DeadLetterQueueProps
+ kind: 22
+ documentation:
+ kind: markdown
+ value: "```wing\nstruct DeadLetterQueueProps\n```\n---\nDead letter queue options.\n### Fields\n- `queue` — `Queue` — Queue to receive messages that failed processing.\n- `maxDeliveryAttempts?` — `num?` — Number of times a message will be processed before being sent to the dead-letter queue."
+ sortText: hh|DeadLetterQueueProps
- label: DomainProps
kind: 22
documentation:
@@ -269,7 +275,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 22
documentation:
kind: markdown
- value: "```wing\nstruct QueueProps\n```\n---\nOptions for `Queue`.\n### Fields\n- `retentionPeriod?` — `duration?` — How long a queue retains a message.\n- `timeout?` — `duration?` — How long a queue's consumers have to process a message."
+ value: "```wing\nstruct QueueProps\n```\n---\nOptions for `Queue`.\n### Fields\n- `dlq?` — `DeadLetterQueueProps?` — A dead-letter queue.\n- `retentionPeriod?` — `duration?` — How long a queue retains a message.\n- `timeout?` — `duration?` — How long a queue's consumers have to process a message."
sortText: hh|QueueProps
- label: QueueSetConsumerOptions
kind: 22
@@ -323,7 +329,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 22
documentation:
kind: markdown
- value: "```wing\nstruct TopicSubscribeQueueOptions extends QueueProps\n```\n---\nOptions for `Topic.subscribeQueue`.\n### Fields\n- `retentionPeriod?` — `duration?`\n- `timeout?` — `duration?`"
+ value: "```wing\nstruct TopicSubscribeQueueOptions extends QueueProps\n```\n---\nOptions for `Topic.subscribeQueue`.\n### Fields\n- `dlq?` — `DeadLetterQueueProps?`\n- `retentionPeriod?` — `duration?`\n- `timeout?` — `duration?`"
sortText: hh|TopicSubscribeQueueOptions
- label: WebsiteDomainOptions
kind: 22
diff --git a/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap b/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap
index 7a230715cf6..1fd2aa61a8f 100644
--- a/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap
+++ b/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap
@@ -47,7 +47,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 7
documentation:
kind: markdown
- value: "```wing\nclass Queue\n```\n---\nA queue.\n\n### Initializer\n- `...props` — `QueueProps?`\n \n - `retentionPeriod?` — `duration?` — How long a queue retains a message.\n - `timeout?` — `duration?` — How long a queue's consumers have to process a message.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `approxSize` — `inflight (): num` — Retrieve the approximate number of messages in the queue.\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `pop` — `inflight (): str?` — Pop a message from the queue.\n- `purge` — `inflight (): void` — Purge all of the messages in the queue.\n- `push` — `inflight (...messages: Array?): void` — Push one or more messages to the queue.\n- `setConsumer` — `preflight (handler: inflight (message: str): void, props: QueueSetConsumerOptions?): Function` — Create a function to consume messages from this queue.\n- `toString` — `preflight (): str` — Returns a string representation of this construct."
+ value: "```wing\nclass Queue\n```\n---\nA queue.\n\n### Initializer\n- `...props` — `QueueProps?`\n \n - `dlq?` — `DeadLetterQueueProps?` — A dead-letter queue.\n - `retentionPeriod?` — `duration?` — How long a queue retains a message.\n - `timeout?` — `duration?` — How long a queue's consumers have to process a message.\n### Fields\n- `node` — `Node` — The tree node.\n### Methods\n- `approxSize` — `inflight (): num` — Retrieve the approximate number of messages in the queue.\n- `isConstruct` — `preflight (x: any): bool` — Checks if `x` is a construct.\n- `onLift` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this resource inflight.\n- `onLiftType` — `preflight (host: IInflightHost, ops: Array): void` — A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.\n- `pop` — `inflight (): str?` — Pop a message from the queue.\n- `purge` — `inflight (): void` — Purge all of the messages in the queue.\n- `push` — `inflight (...messages: Array?): void` — Push one or more messages to the queue.\n- `setConsumer` — `preflight (handler: inflight (message: str): void, props: QueueSetConsumerOptions?): Function` — Create a function to consume messages from this queue.\n- `toString` — `preflight (): str` — Returns a string representation of this construct."
sortText: gg|Queue
- label: Schedule
kind: 7
@@ -229,6 +229,12 @@ source: libs/wingc/src/lsp/completions.rs
kind: markdown
value: "```wing\nstruct CounterProps\n```\n---\nOptions for `Counter`.\n### Fields\n- `initial?` — `num?` — The initial value of the counter."
sortText: hh|CounterProps
+- label: DeadLetterQueueProps
+ kind: 22
+ documentation:
+ kind: markdown
+ value: "```wing\nstruct DeadLetterQueueProps\n```\n---\nDead letter queue options.\n### Fields\n- `queue` — `Queue` — Queue to receive messages that failed processing.\n- `maxDeliveryAttempts?` — `num?` — Number of times a message will be processed before being sent to the dead-letter queue."
+ sortText: hh|DeadLetterQueueProps
- label: DomainProps
kind: 22
documentation:
@@ -269,7 +275,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 22
documentation:
kind: markdown
- value: "```wing\nstruct QueueProps\n```\n---\nOptions for `Queue`.\n### Fields\n- `retentionPeriod?` — `duration?` — How long a queue retains a message.\n- `timeout?` — `duration?` — How long a queue's consumers have to process a message."
+ value: "```wing\nstruct QueueProps\n```\n---\nOptions for `Queue`.\n### Fields\n- `dlq?` — `DeadLetterQueueProps?` — A dead-letter queue.\n- `retentionPeriod?` — `duration?` — How long a queue retains a message.\n- `timeout?` — `duration?` — How long a queue's consumers have to process a message."
sortText: hh|QueueProps
- label: QueueSetConsumerOptions
kind: 22
@@ -323,7 +329,7 @@ source: libs/wingc/src/lsp/completions.rs
kind: 22
documentation:
kind: markdown
- value: "```wing\nstruct TopicSubscribeQueueOptions extends QueueProps\n```\n---\nOptions for `Topic.subscribeQueue`.\n### Fields\n- `retentionPeriod?` — `duration?`\n- `timeout?` — `duration?`"
+ value: "```wing\nstruct TopicSubscribeQueueOptions extends QueueProps\n```\n---\nOptions for `Topic.subscribeQueue`.\n### Fields\n- `dlq?` — `DeadLetterQueueProps?`\n- `retentionPeriod?` — `duration?`\n- `timeout?` — `duration?`"
sortText: hh|TopicSubscribeQueueOptions
- label: WebsiteDomainOptions
kind: 22
diff --git a/libs/wingsdk/src/cloud/queue.md b/libs/wingsdk/src/cloud/queue.md
index 9e769410044..04c656ccffb 100644
--- a/libs/wingsdk/src/cloud/queue.md
+++ b/libs/wingsdk/src/cloud/queue.md
@@ -62,6 +62,22 @@ new cloud.Function(inflight () => {
});
```
+### Adding a dead-letter queue
+
+Creating a queue and adding a dead-letter queue with the maximum number of attempts configured
+
+```ts playground
+bring cloud;
+
+let dlq = new cloud.Queue() as "dead-letter queue";
+let q = new cloud.Queue(
+ dlq: {
+ queue: dlq,
+ maxDeliveryAttempts: 2
+ }
+);
+```
+
### Referencing an external queue
If you would like to reference an existing queue from within your application you can use the
diff --git a/libs/wingsdk/src/cloud/queue.ts b/libs/wingsdk/src/cloud/queue.ts
index 00fc8740c8b..2380d2a7331 100644
--- a/libs/wingsdk/src/cloud/queue.ts
+++ b/libs/wingsdk/src/cloud/queue.ts
@@ -10,6 +10,27 @@ import { Duration, IInflight, Node, Resource } from "../std";
*/
export const QUEUE_FQN = fqnForType("cloud.Queue");
+/**
+ * Dead-letter queue default retries
+ */
+export const DEFAULT_DELIVERY_ATTEMPTS = 1;
+
+/**
+ * Dead letter queue options.
+ */
+export interface DeadLetterQueueProps {
+ /**
+ * Queue to receive messages that failed processing.
+ */
+ readonly queue: Queue;
+ /**
+ * Number of times a message will be processed before being
+ * sent to the dead-letter queue.
+ * @default 1
+ */
+ readonly maxDeliveryAttempts?: number;
+}
+
/**
* Options for `Queue`.
*/
@@ -25,6 +46,12 @@ export interface QueueProps {
* @default 1h
*/
readonly retentionPeriod?: Duration;
+
+ /**
+ * A dead-letter queue.
+ * @default - no dead letter queue
+ */
+ readonly dlq?: DeadLetterQueueProps;
}
/**
diff --git a/libs/wingsdk/src/shared-aws/queue.setconsumer.inflight.ts b/libs/wingsdk/src/shared-aws/queue.setconsumer.inflight.ts
index 2a192a76149..b9b35caaec0 100644
--- a/libs/wingsdk/src/shared-aws/queue.setconsumer.inflight.ts
+++ b/libs/wingsdk/src/shared-aws/queue.setconsumer.inflight.ts
@@ -10,9 +10,17 @@ export class QueueSetConsumerHandlerClient
constructor({ handler }: { handler: IFunctionHandlerClient }) {
this.handler = handler;
}
- public async handle(event: any) {
+ public async handle(event: any): Promise {
+ const batchItemFailures = [];
for (const record of event.Records ?? []) {
- await this.handler.handle(record.body);
+ try {
+ await this.handler.handle(record.body);
+ } catch (error) {
+ batchItemFailures.push({
+ itemIdentifier: record.messageId,
+ });
+ }
}
+ return { batchItemFailures };
}
}
diff --git a/libs/wingsdk/src/target-sim/queue.inflight.ts b/libs/wingsdk/src/target-sim/queue.inflight.ts
index ade75198fb6..8700f5ba0b9 100644
--- a/libs/wingsdk/src/target-sim/queue.inflight.ts
+++ b/libs/wingsdk/src/target-sim/queue.inflight.ts
@@ -5,9 +5,15 @@ import {
QueueSchema,
QueueSubscriber,
EventSubscription,
+ DeadLetterQueueSchema,
ResourceHandle,
} from "./schema-resources";
-import { IFunctionClient, IQueueClient, QUEUE_FQN } from "../cloud";
+import {
+ DEFAULT_DELIVERY_ATTEMPTS,
+ IFunctionClient,
+ IQueueClient,
+ QUEUE_FQN,
+} from "../cloud";
import {
ISimulatorContext,
ISimulatorResourceInstance,
@@ -24,10 +30,12 @@ export class Queue
private _context: ISimulatorContext | undefined;
private readonly timeoutSeconds: number;
private readonly retentionPeriod: number;
+ private readonly dlq?: DeadLetterQueueSchema;
constructor(props: QueueSchema) {
this.timeoutSeconds = props.timeout;
this.retentionPeriod = props.retentionPeriod;
+ this.dlq = props.dlq;
this.processLoop = runEvery(100, async () => this.processMessages()); // every 0.1 seconds
}
@@ -85,7 +93,13 @@ export class Queue
throw new Error("Empty messages are not allowed");
}
for (const message of messages) {
- this.messages.push(new QueueMessage(this.retentionPeriod, message));
+ this.messages.push(
+ new QueueMessage(
+ this.retentionPeriod,
+ DEFAULT_DELIVERY_ATTEMPTS,
+ message
+ )
+ );
}
},
});
@@ -187,8 +201,38 @@ export class Queue
// we don't use invokeAsync here because we want to wait for the function to finish
// and requeue the messages if it fails
- void fnClient
- .invoke(JSON.stringify({ messages: messagesPayload }))
+ await fnClient
+ .invoke(JSON.stringify({ messages: messages }))
+ .then((result) => {
+ if (this.dlq && result) {
+ const errorList = JSON.parse(result);
+ let retriesMessages = [];
+ for (const msg of errorList) {
+ if (
+ msg.remainingDeliveryAttempts < this.dlq.maxDeliveryAttempts
+ ) {
+ msg.remainingDeliveryAttempts++;
+ retriesMessages.push(msg);
+ } else {
+ let dlq = this.context.getClient(
+ this.dlq.dlqHandler
+ ) as IQueueClient;
+ void dlq.push(msg.payload).catch((err) => {
+ this.context.addTrace({
+ type: TraceType.RESOURCE,
+ data: {
+ message: `Pushing messages to the dead-letter queue generates an error -> ${err}`,
+ },
+ sourcePath: this.context.resourcePath,
+ sourceType: QUEUE_FQN,
+ timestamp: new Date().toISOString(),
+ });
+ });
+ }
+ }
+ this.messages.push(...retriesMessages);
+ }
+ })
.catch((err) => {
// If the function is at a concurrency limit, pretend we just didn't call it
if (
@@ -238,12 +282,18 @@ export class Queue
class QueueMessage {
public readonly retentionTimeout: Date;
public readonly payload: string;
+ public remainingDeliveryAttempts: number;
- constructor(retentionPeriod: number, message: string) {
+ constructor(
+ retentionPeriod: number,
+ remainingDeliveryAttempts: number,
+ message: string
+ ) {
const currentTime = new Date();
currentTime.setSeconds(retentionPeriod + currentTime.getSeconds());
this.retentionTimeout = currentTime;
this.payload = message;
+ this.remainingDeliveryAttempts = remainingDeliveryAttempts;
}
}
diff --git a/libs/wingsdk/src/target-sim/queue.setconsumer.inflight.ts b/libs/wingsdk/src/target-sim/queue.setconsumer.inflight.ts
index 55dad14f8b0..db2b90ca570 100644
--- a/libs/wingsdk/src/target-sim/queue.setconsumer.inflight.ts
+++ b/libs/wingsdk/src/target-sim/queue.setconsumer.inflight.ts
@@ -9,11 +9,18 @@ export class QueueSetConsumerHandlerClient implements IFunctionHandlerClient {
this.handler = handler;
}
public async handle(event?: string) {
+ const batchItemFailures = [];
let parsed = JSON.parse(event ?? "{}");
if (!parsed.messages) throw new Error('No "messages" field in event.');
for (const $message of parsed.messages) {
- await this.handler.handle($message);
+ try {
+ await this.handler.handle($message.payload);
+ } catch (error) {
+ batchItemFailures.push($message);
+ }
}
- return undefined;
+ return batchItemFailures.length > 0
+ ? JSON.stringify(batchItemFailures)
+ : undefined;
}
}
diff --git a/libs/wingsdk/src/target-sim/queue.ts b/libs/wingsdk/src/target-sim/queue.ts
index 006482ad3ac..30d5d661b2b 100644
--- a/libs/wingsdk/src/target-sim/queue.ts
+++ b/libs/wingsdk/src/target-sim/queue.ts
@@ -9,6 +9,7 @@ import {
import { Policy } from "./policy";
import { ISimulatorResource } from "./resource";
import { QueueSchema } from "./schema-resources";
+import { simulatorHandleToken } from "./tokens";
import { bindSimulatorResource, makeSimulatorJsClient } from "./util";
import * as cloud from "../cloud";
import { NotImplementedError } from "../core/errors";
@@ -24,6 +25,7 @@ import { Duration, IInflightHost, Node, SDK_SOURCE_MODULE } from "../std";
export class Queue extends cloud.Queue implements ISimulatorResource {
private readonly timeout: Duration;
private readonly retentionPeriod: Duration;
+ private readonly dlq?: cloud.DeadLetterQueueProps;
private readonly policy: Policy;
constructor(scope: Construct, id: string, props: cloud.QueueProps = {}) {
@@ -50,6 +52,18 @@ export class Queue extends cloud.Queue implements ISimulatorResource {
}
this.policy = new Policy(this, "Policy", { principal: this });
+
+ if (props.dlq && props.dlq.queue) {
+ this.dlq = props.dlq;
+
+ this.policy.addStatement(this.dlq.queue, cloud.QueueInflightMethods.PUSH);
+
+ Node.of(this).addConnection({
+ source: this,
+ target: this.dlq.queue,
+ name: "dead-letter queue",
+ });
+ }
}
/** @internal */
@@ -136,6 +150,13 @@ export class Queue extends cloud.Queue implements ISimulatorResource {
const props: QueueSchema = {
timeout: this.timeout.seconds,
retentionPeriod: this.retentionPeriod.seconds,
+ dlq: this.dlq
+ ? {
+ dlqHandler: simulatorHandleToken(this.dlq.queue),
+ maxDeliveryAttempts:
+ this.dlq.maxDeliveryAttempts ?? cloud.DEFAULT_DELIVERY_ATTEMPTS,
+ }
+ : undefined,
};
return {
type: cloud.QUEUE_FQN,
diff --git a/libs/wingsdk/src/target-sim/schema-resources.ts b/libs/wingsdk/src/target-sim/schema-resources.ts
index 91a56fc763f..a482bbeb451 100644
--- a/libs/wingsdk/src/target-sim/schema-resources.ts
+++ b/libs/wingsdk/src/target-sim/schema-resources.ts
@@ -48,12 +48,21 @@ export interface FunctionSchema {
/** Runtime attributes for cloud.Function */
export interface FunctionAttributes {}
+export interface DeadLetterQueueSchema {
+ /** Dead-letter queue handler token */
+ dlqHandler: string;
+ /** Number of time a message will be processed */
+ maxDeliveryAttempts: number;
+}
+
/** Schema for cloud.Queue */
export interface QueueSchema {
/** How long a queue's consumers have to process a message, in seconds */
readonly timeout: number;
/** How long a queue retains a message, in seconds */
readonly retentionPeriod: number;
+ /** Dead-letter queue options */
+ readonly dlq?: DeadLetterQueueSchema;
}
/** Runtime attributes for cloud.Queue */
diff --git a/libs/wingsdk/src/target-tf-aws/queue.ts b/libs/wingsdk/src/target-tf-aws/queue.ts
index 988ee0ac540..6a38a525e5b 100644
--- a/libs/wingsdk/src/target-tf-aws/queue.ts
+++ b/libs/wingsdk/src/target-tf-aws/queue.ts
@@ -10,6 +10,7 @@ import { convertBetweenHandlers } from "../shared/convert";
import { NameOptions, ResourceNames } from "../shared/resource-names";
import { IAwsQueue } from "../shared-aws";
import { calculateQueuePermissions } from "../shared-aws/permissions";
+import { Queue as AwsQueue } from "../shared-aws/queue";
import { Duration, IInflightHost, Node } from "../std";
/**
@@ -32,15 +33,32 @@ export class Queue extends cloud.Queue implements IAwsQueue {
constructor(scope: Construct, id: string, props: cloud.QueueProps = {}) {
super(scope, id, props);
- this.queue = new SqsQueue(this, "Default", {
- visibilityTimeoutSeconds: props.timeout
- ? props.timeout.seconds
- : Duration.fromSeconds(30).seconds,
- messageRetentionSeconds: props.retentionPeriod
- ? props.retentionPeriod.seconds
- : Duration.fromHours(1).seconds,
- name: ResourceNames.generateName(this, NAME_OPTS),
- });
+ const queueOpt = props.dlq
+ ? {
+ visibilityTimeoutSeconds: props.timeout
+ ? props.timeout.seconds
+ : Duration.fromSeconds(30).seconds,
+ messageRetentionSeconds: props.retentionPeriod
+ ? props.retentionPeriod.seconds
+ : Duration.fromHours(1).seconds,
+ name: ResourceNames.generateName(this, NAME_OPTS),
+ redrivePolicy: JSON.stringify({
+ deadLetterTargetArn: AwsQueue.from(props.dlq.queue)?.queueArn,
+ maxReceiveCount:
+ props.dlq.maxDeliveryAttempts ?? cloud.DEFAULT_DELIVERY_ATTEMPTS,
+ }),
+ }
+ : {
+ visibilityTimeoutSeconds: props.timeout
+ ? props.timeout.seconds
+ : Duration.fromSeconds(30).seconds,
+ messageRetentionSeconds: props.retentionPeriod
+ ? props.retentionPeriod.seconds
+ : Duration.fromHours(1).seconds,
+ name: ResourceNames.generateName(this, NAME_OPTS),
+ };
+
+ this.queue = new SqsQueue(this, "Default", queueOpt);
}
/** @internal */
@@ -99,6 +117,7 @@ export class Queue extends cloud.Queue implements IAwsQueue {
functionName: fn.functionName,
eventSourceArn: this.queue.arn,
batchSize: props.batchSize ?? 1,
+ functionResponseTypes: ["ReportBatchItemFailures"], // It allows the function to return the messages that failed to the queue
});
Node.of(this).addConnection({
diff --git a/libs/wingsdk/test/target-tf-aws/__snapshots__/captures.test.ts.snap b/libs/wingsdk/test/target-tf-aws/__snapshots__/captures.test.ts.snap
index ecf8f57cca0..065e1c16ea2 100644
--- a/libs/wingsdk/test/target-tf-aws/__snapshots__/captures.test.ts.snap
+++ b/libs/wingsdk/test/target-tf-aws/__snapshots__/captures.test.ts.snap
@@ -222,6 +222,9 @@ exports[`function with a queue binding 3`] = `
"batch_size": 1,
"event_source_arn": "\${aws_sqs_queue.Queue.arn}",
"function_name": "\${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures",
+ ],
},
},
"aws_lambda_function": {
diff --git a/libs/wingsdk/test/target-tf-aws/__snapshots__/queue.test.ts.snap b/libs/wingsdk/test/target-tf-aws/__snapshots__/queue.test.ts.snap
index 28e841f6eb5..bf2f4ed0b91 100644
--- a/libs/wingsdk/test/target-tf-aws/__snapshots__/queue.test.ts.snap
+++ b/libs/wingsdk/test/target-tf-aws/__snapshots__/queue.test.ts.snap
@@ -831,6 +831,9 @@ exports[`queue with a consumer function 2`] = `
"batch_size": 1,
"event_source_arn": "\${aws_sqs_queue.Queue.arn}",
"function_name": "\${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures",
+ ],
},
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/platform.ts.snap b/tools/hangar/__snapshots__/platform.ts.snap
index 61e7c5e154e..9c3e6eefe06 100644
--- a/tools/hangar/__snapshots__/platform.ts.snap
+++ b/tools/hangar/__snapshots__/platform.ts.snap
@@ -74,6 +74,9 @@ exports[`Multiple platforms > only first platform app is used 1`] = `
"batch_size": 1,
"event_source_arn": "\${aws_sqs_queue.Queue.arn}",
"function_name": "\${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures",
+ ],
},
},
"aws_lambda_function": {
@@ -236,6 +239,9 @@ exports[`Platform examples > AWS target platform > permission-boundary.js 1`] =
"batch_size": 1,
"event_source_arn": "\${aws_sqs_queue.Queue.arn}",
"function_name": "\${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures",
+ ],
},
},
"aws_lambda_function": {
@@ -467,6 +473,9 @@ exports[`Platform examples > AWS target platform > replicate-s3.js 1`] = `
"batch_size": 1,
"event_source_arn": "\${aws_sqs_queue.Queue.arn}",
"function_name": "\${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures",
+ ],
},
},
"aws_lambda_function": {
@@ -746,6 +755,9 @@ exports[`Platform examples > AWS target platform > tf-backend.js > azurerm backe
"batch_size": 1,
"event_source_arn": "\${aws_sqs_queue.Queue.arn}",
"function_name": "\${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures",
+ ],
},
},
"aws_lambda_function": {
@@ -907,6 +919,9 @@ exports[`Platform examples > AWS target platform > tf-backend.js > gcp backend 1
"batch_size": 1,
"event_source_arn": "\${aws_sqs_queue.Queue.arn}",
"function_name": "\${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures",
+ ],
},
},
"aws_lambda_function": {
@@ -1068,6 +1083,9 @@ exports[`Platform examples > AWS target platform > tf-backend.js > s3 backend 1`
"batch_size": 1,
"event_source_arn": "\${aws_sqs_queue.Queue.arn}",
"function_name": "\${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures",
+ ],
},
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/dead-letter-queue.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/dead-letter-queue.test.w_compile_tf-aws.md
new file mode 100644
index 00000000000..74d09daa612
--- /dev/null
+++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/dead-letter-queue.test.w_compile_tf-aws.md
@@ -0,0 +1,307 @@
+# [dead-letter-queue.test.w](../../../../../../examples/tests/sdk_tests/queue/dead-letter-queue.test.w) | compile | tf-aws
+
+## main.tf.json
+```json
+{
+ "//": {
+ "metadata": {
+ "backend": "local",
+ "stackName": "root",
+ "version": "0.20.3"
+ },
+ "outputs": {}
+ },
+ "provider": {
+ "aws": [
+ {}
+ ]
+ },
+ "resource": {
+ "aws_cloudwatch_log_group": {
+ "queuewithoutretries-SetConsumer0_CloudwatchLogGroup_9265B40C": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue without retries-SetConsumer0/CloudwatchLogGroup",
+ "uniqueId": "queuewithoutretries-SetConsumer0_CloudwatchLogGroup_9265B40C"
+ }
+ },
+ "name": "/aws/lambda/queue-without-retries-SetConsumer0-c8ba2958",
+ "retention_in_days": 30
+ },
+ "queuewithretries-SetConsumer0_CloudwatchLogGroup_98D9A088": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue with retries-SetConsumer0/CloudwatchLogGroup",
+ "uniqueId": "queuewithretries-SetConsumer0_CloudwatchLogGroup_98D9A088"
+ }
+ },
+ "name": "/aws/lambda/queue-with-retries-SetConsumer0-c82b1a26",
+ "retention_in_days": 30
+ }
+ },
+ "aws_dynamodb_table": {
+ "Counter": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/Counter/Default",
+ "uniqueId": "Counter"
+ }
+ },
+ "attribute": [
+ {
+ "name": "id",
+ "type": "S"
+ }
+ ],
+ "billing_mode": "PAY_PER_REQUEST",
+ "hash_key": "id",
+ "name": "wing-counter-Counter-c824ef62"
+ }
+ },
+ "aws_iam_role": {
+ "queuewithoutretries-SetConsumer0_IamRole_F76F585E": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue without retries-SetConsumer0/IamRole",
+ "uniqueId": "queuewithoutretries-SetConsumer0_IamRole_F76F585E"
+ }
+ },
+ "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}"
+ },
+ "queuewithretries-SetConsumer0_IamRole_B95C0A26": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue with retries-SetConsumer0/IamRole",
+ "uniqueId": "queuewithretries-SetConsumer0_IamRole_B95C0A26"
+ }
+ },
+ "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}"
+ }
+ },
+ "aws_iam_role_policy": {
+ "queuewithoutretries-SetConsumer0_IamRolePolicy_2603CFA0": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue without retries-SetConsumer0/IamRolePolicy",
+ "uniqueId": "queuewithoutretries-SetConsumer0_IamRolePolicy_2603CFA0"
+ }
+ },
+ "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"sqs:ReceiveMessage\",\"sqs:ChangeMessageVisibility\",\"sqs:GetQueueUrl\",\"sqs:DeleteMessage\",\"sqs:GetQueueAttributes\"],\"Resource\":[\"${aws_sqs_queue.queuewithoutretries.arn}\"],\"Effect\":\"Allow\"},{\"Action\":[\"dynamodb:UpdateItem\"],\"Resource\":[\"${aws_dynamodb_table.Counter.arn}\"],\"Effect\":\"Allow\"}]}",
+ "role": "${aws_iam_role.queuewithoutretries-SetConsumer0_IamRole_F76F585E.name}"
+ },
+ "queuewithretries-SetConsumer0_IamRolePolicy_245846E1": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue with retries-SetConsumer0/IamRolePolicy",
+ "uniqueId": "queuewithretries-SetConsumer0_IamRolePolicy_245846E1"
+ }
+ },
+ "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"sqs:ReceiveMessage\",\"sqs:ChangeMessageVisibility\",\"sqs:GetQueueUrl\",\"sqs:DeleteMessage\",\"sqs:GetQueueAttributes\"],\"Resource\":[\"${aws_sqs_queue.queuewithretries.arn}\"],\"Effect\":\"Allow\"},{\"Action\":[\"dynamodb:UpdateItem\"],\"Resource\":[\"${aws_dynamodb_table.Counter.arn}\"],\"Effect\":\"Allow\"}]}",
+ "role": "${aws_iam_role.queuewithretries-SetConsumer0_IamRole_B95C0A26.name}"
+ }
+ },
+ "aws_iam_role_policy_attachment": {
+ "queuewithoutretries-SetConsumer0_IamRolePolicyAttachment_559EECE4": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue without retries-SetConsumer0/IamRolePolicyAttachment",
+ "uniqueId": "queuewithoutretries-SetConsumer0_IamRolePolicyAttachment_559EECE4"
+ }
+ },
+ "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+ "role": "${aws_iam_role.queuewithoutretries-SetConsumer0_IamRole_F76F585E.name}"
+ },
+ "queuewithretries-SetConsumer0_IamRolePolicyAttachment_831C8294": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue with retries-SetConsumer0/IamRolePolicyAttachment",
+ "uniqueId": "queuewithretries-SetConsumer0_IamRolePolicyAttachment_831C8294"
+ }
+ },
+ "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+ "role": "${aws_iam_role.queuewithretries-SetConsumer0_IamRole_B95C0A26.name}"
+ }
+ },
+ "aws_lambda_event_source_mapping": {
+ "queuewithoutretries_EventSourceMapping_963C2B4C": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue without retries/EventSourceMapping",
+ "uniqueId": "queuewithoutretries_EventSourceMapping_963C2B4C"
+ }
+ },
+ "batch_size": 1,
+ "event_source_arn": "${aws_sqs_queue.queuewithoutretries.arn}",
+ "function_name": "${aws_lambda_function.queuewithoutretries-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
+ },
+ "queuewithretries_EventSourceMapping_A0EC80F3": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue with retries/EventSourceMapping",
+ "uniqueId": "queuewithretries_EventSourceMapping_A0EC80F3"
+ }
+ },
+ "batch_size": 1,
+ "event_source_arn": "${aws_sqs_queue.queuewithretries.arn}",
+ "function_name": "${aws_lambda_function.queuewithretries-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
+ }
+ },
+ "aws_lambda_function": {
+ "queuewithoutretries-SetConsumer0": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue without retries-SetConsumer0/Default",
+ "uniqueId": "queuewithoutretries-SetConsumer0"
+ }
+ },
+ "architectures": [
+ "arm64"
+ ],
+ "environment": {
+ "variables": {
+ "DYNAMODB_TABLE_NAME_6cb5a3a4": "${aws_dynamodb_table.Counter.name}",
+ "NODE_OPTIONS": "--enable-source-maps",
+ "WING_FUNCTION_NAME": "queue-without-retries-SetConsumer0-c8ba2958",
+ "WING_TARGET": "tf-aws"
+ }
+ },
+ "function_name": "queue-without-retries-SetConsumer0-c8ba2958",
+ "handler": "index.handler",
+ "memory_size": 1024,
+ "publish": true,
+ "role": "${aws_iam_role.queuewithoutretries-SetConsumer0_IamRole_F76F585E.arn}",
+ "runtime": "nodejs20.x",
+ "s3_bucket": "${aws_s3_bucket.Code.bucket}",
+ "s3_key": "${aws_s3_object.queuewithoutretries-SetConsumer0_S3Object_ECD1395D.key}",
+ "timeout": "${aws_sqs_queue.queuewithoutretries.visibility_timeout_seconds}",
+ "vpc_config": {
+ "security_group_ids": [],
+ "subnet_ids": []
+ }
+ },
+ "queuewithretries-SetConsumer0": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue with retries-SetConsumer0/Default",
+ "uniqueId": "queuewithretries-SetConsumer0"
+ }
+ },
+ "architectures": [
+ "arm64"
+ ],
+ "environment": {
+ "variables": {
+ "DYNAMODB_TABLE_NAME_6cb5a3a4": "${aws_dynamodb_table.Counter.name}",
+ "NODE_OPTIONS": "--enable-source-maps",
+ "WING_FUNCTION_NAME": "queue-with-retries-SetConsumer0-c82b1a26",
+ "WING_TARGET": "tf-aws"
+ }
+ },
+ "function_name": "queue-with-retries-SetConsumer0-c82b1a26",
+ "handler": "index.handler",
+ "memory_size": 1024,
+ "publish": true,
+ "role": "${aws_iam_role.queuewithretries-SetConsumer0_IamRole_B95C0A26.arn}",
+ "runtime": "nodejs20.x",
+ "s3_bucket": "${aws_s3_bucket.Code.bucket}",
+ "s3_key": "${aws_s3_object.queuewithretries-SetConsumer0_S3Object_29ED03EE.key}",
+ "timeout": "${aws_sqs_queue.queuewithretries.visibility_timeout_seconds}",
+ "vpc_config": {
+ "security_group_ids": [],
+ "subnet_ids": []
+ }
+ }
+ },
+ "aws_s3_bucket": {
+ "Code": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Code",
+ "uniqueId": "Code"
+ }
+ },
+ "bucket_prefix": "code-c84a50b1-"
+ }
+ },
+ "aws_s3_object": {
+ "queuewithoutretries-SetConsumer0_S3Object_ECD1395D": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue without retries-SetConsumer0/S3Object",
+ "uniqueId": "queuewithoutretries-SetConsumer0_S3Object_ECD1395D"
+ }
+ },
+ "bucket": "${aws_s3_bucket.Code.bucket}",
+ "key": "",
+ "source": ""
+ },
+ "queuewithretries-SetConsumer0_S3Object_29ED03EE": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue with retries-SetConsumer0/S3Object",
+ "uniqueId": "queuewithretries-SetConsumer0_S3Object_29ED03EE"
+ }
+ },
+ "bucket": "${aws_s3_bucket.Code.bucket}",
+ "key": "",
+ "source": ""
+ }
+ },
+ "aws_sqs_queue": {
+ "dlqwithoutretries": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/dlq without retries/Default",
+ "uniqueId": "dlqwithoutretries"
+ }
+ },
+ "message_retention_seconds": 3600,
+ "name": "dlq-without-retries-c83c0330",
+ "visibility_timeout_seconds": 30
+ },
+ "dlqwithretries": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/dlq with retries/Default",
+ "uniqueId": "dlqwithretries"
+ }
+ },
+ "message_retention_seconds": 3600,
+ "name": "dlq-with-retries-c877f5c7",
+ "visibility_timeout_seconds": 30
+ },
+ "queuewithoutretries": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue without retries/Default",
+ "uniqueId": "queuewithoutretries"
+ }
+ },
+ "message_retention_seconds": 3600,
+ "name": "queue-without-retries-c8a5001c",
+ "redrive_policy": "{\"deadLetterTargetArn\":\"${aws_sqs_queue.dlqwithoutretries.arn}\",\"maxReceiveCount\":1}",
+ "visibility_timeout_seconds": 30
+ },
+ "queuewithretries": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/queue with retries/Default",
+ "uniqueId": "queuewithretries"
+ }
+ },
+ "message_retention_seconds": 3600,
+ "name": "queue-with-retries-c8a06dc7",
+ "redrive_policy": "{\"deadLetterTargetArn\":\"${aws_sqs_queue.dlqwithretries.arn}\",\"maxReceiveCount\":2}",
+ "visibility_timeout_seconds": 30
+ }
+ }
+ }
+}
+```
+
diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/dead-letter-queue.test.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/dead-letter-queue.test.w_test_sim.md
new file mode 100644
index 00000000000..f35d5d2ed2d
--- /dev/null
+++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/dead-letter-queue.test.w_test_sim.md
@@ -0,0 +1,13 @@
+# [dead-letter-queue.test.w](../../../../../../examples/tests/sdk_tests/queue/dead-letter-queue.test.w) | test | sim
+
+## stdout.log
+```log
+pass ─ dead-letter-queue.test.wsim » root/env0/one execution and send fail message to dead-letter queue
+pass ─ dead-letter-queue.test.wsim » root/env1/one execution, two retries and send the fail message to dead-letter queue
+
+Tests 2 passed (2)
+Snapshots 1 skipped
+Test Files 1 passed (1)
+Duration
+```
+
diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/queue-ref.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/queue-ref.test.w_compile_tf-aws.md
index 5559abc4cb1..598c74b34e6 100644
--- a/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/queue-ref.test.w_compile_tf-aws.md
+++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/queue-ref.test.w_compile_tf-aws.md
@@ -249,7 +249,10 @@
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.Queue.arn}",
- "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
}
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/set_consumer.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/set_consumer.test.w_compile_tf-aws.md
index 6fea628fc06..e1da8b967b6 100644
--- a/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/set_consumer.test.w_compile_tf-aws.md
+++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/set_consumer.test.w_compile_tf-aws.md
@@ -149,7 +149,10 @@
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.Queue.arn}",
- "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
},
"q2_EventSourceMapping_F484014F": {
"//": {
@@ -160,7 +163,10 @@
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.q2.arn}",
- "function_name": "${aws_lambda_function.q2-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.q2-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
}
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/topic/subscribe-queue.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/topic/subscribe-queue.test.w_compile_tf-aws.md
index 8a0674fd361..f833a206df1 100644
--- a/tools/hangar/__snapshots__/test_corpus/sdk_tests/topic/subscribe-queue.test.w_compile_tf-aws.md
+++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/topic/subscribe-queue.test.w_compile_tf-aws.md
@@ -171,7 +171,10 @@
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.q1.arn}",
- "function_name": "${aws_lambda_function.q1-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.q1-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
},
"q2_EventSourceMapping_F484014F": {
"//": {
@@ -182,7 +185,10 @@
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.q2.arn}",
- "function_name": "${aws_lambda_function.q2-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.q2-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
}
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/test_corpus/valid/captures.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/captures.test.w_compile_tf-aws.md
index 746445c0c10..01ee869a182 100644
--- a/tools/hangar/__snapshots__/test_corpus/valid/captures.test.w_compile_tf-aws.md
+++ b/tools/hangar/__snapshots__/test_corpus/valid/captures.test.w_compile_tf-aws.md
@@ -349,7 +349,10 @@ module.exports = function({ $headers }) {
},
"batch_size": 5,
"event_source_arn": "${aws_sqs_queue.Queue.arn}",
- "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
}
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/test_corpus/valid/file_counter.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/file_counter.test.w_compile_tf-aws.md
index bd433ead881..7cab4a39fc8 100644
--- a/tools/hangar/__snapshots__/test_corpus/valid/file_counter.test.w_compile_tf-aws.md
+++ b/tools/hangar/__snapshots__/test_corpus/valid/file_counter.test.w_compile_tf-aws.md
@@ -115,7 +115,10 @@ module.exports = function({ $bucket, $counter }) {
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.Queue.arn}",
- "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
}
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/test_corpus/valid/hello.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/hello.test.w_compile_tf-aws.md
index a89822806fa..27941b931ba 100644
--- a/tools/hangar/__snapshots__/test_corpus/valid/hello.test.w_compile_tf-aws.md
+++ b/tools/hangar/__snapshots__/test_corpus/valid/hello.test.w_compile_tf-aws.md
@@ -94,7 +94,10 @@ module.exports = function({ $bucket }) {
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.Queue.arn}",
- "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
}
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/test_corpus/valid/inflight-subscribers.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/inflight-subscribers.test.w_compile_tf-aws.md
index ae14796da53..32e4f4a90e5 100644
--- a/tools/hangar/__snapshots__/test_corpus/valid/inflight-subscribers.test.w_compile_tf-aws.md
+++ b/tools/hangar/__snapshots__/test_corpus/valid/inflight-subscribers.test.w_compile_tf-aws.md
@@ -153,7 +153,10 @@ module.exports = function({ }) {
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.Queue.arn}",
- "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
}
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md
index fdc84d01919..8159814e3fa 100644
--- a/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md
+++ b/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md
@@ -223,7 +223,10 @@ module.exports = function({ $queue, $r, $r2, $util_Util }) {
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.Queue.arn}",
- "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
}
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/test_corpus/valid/resource.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/resource.test.w_compile_tf-aws.md
index f87bdd61606..c43fc4d5a66 100644
--- a/tools/hangar/__snapshots__/test_corpus/valid/resource.test.w_compile_tf-aws.md
+++ b/tools/hangar/__snapshots__/test_corpus/valid/resource.test.w_compile_tf-aws.md
@@ -397,7 +397,10 @@ module.exports = function({ }) {
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.BigPublisher_Queue_2C024F97.arn}",
- "function_name": "${aws_lambda_function.BigPublisher_Queue-SetConsumer0_55896C65.function_name}"
+ "function_name": "${aws_lambda_function.BigPublisher_Queue-SetConsumer0_55896C65.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
}
},
"aws_lambda_function": {
diff --git a/tools/hangar/__snapshots__/test_corpus/valid/while_loop_await.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/while_loop_await.test.w_compile_tf-aws.md
index c73bf15bf1c..5baaa695c11 100644
--- a/tools/hangar/__snapshots__/test_corpus/valid/while_loop_await.test.w_compile_tf-aws.md
+++ b/tools/hangar/__snapshots__/test_corpus/valid/while_loop_await.test.w_compile_tf-aws.md
@@ -100,7 +100,10 @@ module.exports = function({ }) {
},
"batch_size": 1,
"event_source_arn": "${aws_sqs_queue.Queue.arn}",
- "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}"
+ "function_name": "${aws_lambda_function.Queue-SetConsumer0.function_name}",
+ "function_response_types": [
+ "ReportBatchItemFailures"
+ ]
}
},
"aws_lambda_function": {