diff --git a/apps/jsii-docgen/src/docgen/view/api-reference.ts b/apps/jsii-docgen/src/docgen/view/api-reference.ts
index 8a408a03597..7d578422fef 100644
--- a/apps/jsii-docgen/src/docgen/view/api-reference.ts
+++ b/apps/jsii-docgen/src/docgen/view/api-reference.ts
@@ -78,7 +78,7 @@ export class ApiReference {
this.constructs = new Constructs(
transpile,
classes,
- this.searchableInterfaces(interfaces)
+ this.searchableInterfaces([...assembly.allInterfaces])
);
this.classes = new Classes(transpile, classes);
this.structs = new Structs(transpile, interfaces);
diff --git a/docs/docs/04-standard-library/aws/api-reference.md b/docs/docs/04-standard-library/aws/api-reference.md
index d609e8a8b54..af9b67b7d52 100644
--- a/docs/docs/04-standard-library/aws/api-reference.md
+++ b/docs/docs/04-standard-library/aws/api-reference.md
@@ -146,6 +146,155 @@ The IAM certificate identifier value.
---
+### QueueRef
+
+A reference to an external SQS queue.
+
+#### Initializers
+
+```wing
+bring aws;
+
+new aws.QueueRef(queueArn: str);
+```
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| queueArn
| str
| *No description.* |
+
+---
+
+##### `queueArn`Required
+
+- *Type:* str
+
+---
+
+#### Methods
+
+##### Inflight Methods
+
+| **Name** | **Description** |
+| --- | --- |
+| approxSize
| Retrieve the approximate number of messages in the queue. |
+| pop
| Pop a message from the queue. |
+| purge
| Purge all of the messages in the queue. |
+| push
| Push one or more messages to the queue. |
+
+---
+
+##### `approxSize`
+
+```wing
+inflight approxSize(): num
+```
+
+Retrieve the approximate number of messages in the queue.
+
+##### `pop`
+
+```wing
+inflight pop(): str
+```
+
+Pop a message from the queue.
+
+##### `purge`
+
+```wing
+inflight purge(): void
+```
+
+Purge all of the messages in the queue.
+
+##### `push`
+
+```wing
+inflight push(...messages: Array): void
+```
+
+Push one or more messages to the queue.
+
+###### `messages`Required
+
+- *Type:* str
+
+Payload to send to the queue.
+
+Each message must be non-empty.
+
+---
+
+#### Static Functions
+
+| **Name** | **Description** |
+| --- | --- |
+| onLiftType
| A hook called by the Wing compiler once for each inflight host that needs to use this type inflight. |
+
+---
+
+##### `onLiftType`
+
+```wing
+bring aws;
+
+aws.QueueRef.onLiftType(host: IInflightHost, ops: MutArray);
+```
+
+A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.
+
+The list of requested inflight methods
+needed by the inflight host are given by `ops`.
+
+This method is commonly used for adding permissions, environment variables, or
+other capabilities to the inflight host.
+
+###### `host`Required
+
+- *Type:* IInflightHost
+
+---
+
+###### `ops`Required
+
+- *Type:* MutArray<str>
+
+---
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| node
| constructs.Node
| The tree node. |
+| queueArn
| str
| The ARN of this queue. |
+
+---
+
+##### `node`Required
+
+```wing
+node: Node;
+```
+
+- *Type:* constructs.Node
+
+The tree node.
+
+---
+
+##### `queueArn`Required
+
+```wing
+queueArn: str;
+```
+
+- *Type:* str
+
+The ARN of this queue.
+
+---
+
+
## Classes
### Api
diff --git a/docs/docs/04-standard-library/cloud/queue.md b/docs/docs/04-standard-library/cloud/queue.md
index 43437098810..6ec262f096d 100644
--- a/docs/docs/04-standard-library/cloud/queue.md
+++ b/docs/docs/04-standard-library/cloud/queue.md
@@ -43,7 +43,7 @@ new cloud.Function(inflight () => {
### Using Queue inflight api
-Pusing messages, popping them, and purge
+Pushing messages, popping them, and purging.
```ts playground
bring cloud;
@@ -62,6 +62,30 @@ new cloud.Function(inflight () => {
});
```
+### Referencing an external queue
+
+If you would like to reference an existing queue from within your application you can use the
+`QueueRef` classes in the target-specific namespaces.
+
+> This is currently only supported for `aws`.
+
+The following example defines a reference to an Amazon SQS queue with a specific ARN and sends a
+message to the queue from the function:
+
+```js
+bring aws;
+
+let outbox = new aws.QueueRef("arn:aws:sqs:us-east-1:111111111111:Outbox");
+
+new cloud.Function(inflight () => {
+ outbox.push("send an email");
+});
+```
+
+This works both when running in the simulator (requires AWS credentials on the developer's machine)
+and when deployed to AWS. When this is deployed to AWS, the AWS Lambda IAM policy will include the
+needed permissions.
+
## Target-specific details
### Simulator (`sim`)
diff --git a/docs/docs/04-standard-library/ui/api-reference.md b/docs/docs/04-standard-library/ui/api-reference.md
index df5a171b82e..c5cd6c5b6c6 100644
--- a/docs/docs/04-standard-library/ui/api-reference.md
+++ b/docs/docs/04-standard-library/ui/api-reference.md
@@ -519,10 +519,24 @@ let FieldProps = ui.FieldProps{ ... };
| **Name** | **Type** | **Description** |
| --- | --- | --- |
+| link
| bool
| Indicates that this field is a link. |
| refreshRate
| duration
| How often the field should be refreshed. |
---
+##### `link`Optional
+
+```wing
+link: bool;
+```
+
+- *Type:* bool
+- *Default:* false
+
+Indicates that this field is a link.
+
+---
+
##### `refreshRate`Optional
```wing
diff --git a/examples/tests/sdk_tests/queue/queue-ref.test.w b/examples/tests/sdk_tests/queue/queue-ref.test.w
new file mode 100644
index 00000000000..fff25baefa4
--- /dev/null
+++ b/examples/tests/sdk_tests/queue/queue-ref.test.w
@@ -0,0 +1,54 @@
+bring cloud;
+bring aws;
+bring util;
+
+// a really cool way to test a "ref" resource: basically, we define a cloud.Queue
+// and then we define a `QueueRef` that references it's ARN. nice, ha?
+
+let c = new cloud.Counter();
+let q = new cloud.Queue();
+
+q.setConsumer(inflight () => {
+ c.inc();
+});
+
+// this will only work if we are testing on tf-aws
+if let arn = aws.Queue.from(q)?.queueArn {
+ let qref = new aws.QueueRef(arn);
+
+ new cloud.Function(inflight () => {
+ qref.push("hello");
+ qref.pop();
+ });
+
+ test "can push to an external queue (QueueRef)" {
+ qref.push("m1");
+ qref.push("m2");
+ qref.push("m3");
+ util.waitUntil(() => {
+ return c.peek() == 3;
+ });
+ }
+}
+
+if util.env("WING_TARGET") == "sim" {
+ bring expect;
+
+ let dummyArn = "arn:aws:sqs:us-east-1:111111111111:Queue-11111111";
+ let qr = new aws.QueueRef(dummyArn);
+
+ test "queueArn returns the arn" {
+ expect.equal(dummyArn, qr.queueArn);
+ }
+
+ test "push() sends a request to aws, fails because we are using a dummy queue" {
+ let var err = false;
+ try {
+ qr.push("foo");
+ } catch e {
+ err = true;
+ }
+
+ assert(err);
+ }
+}
\ No newline at end of file
diff --git a/libs/wingsdk/src/cloud/queue.md b/libs/wingsdk/src/cloud/queue.md
index 3bc93e71a7e..9e769410044 100644
--- a/libs/wingsdk/src/cloud/queue.md
+++ b/libs/wingsdk/src/cloud/queue.md
@@ -43,7 +43,7 @@ new cloud.Function(inflight () => {
### Using Queue inflight api
-Pusing messages, popping them, and purge
+Pushing messages, popping them, and purging.
```ts playground
bring cloud;
@@ -62,6 +62,30 @@ new cloud.Function(inflight () => {
});
```
+### Referencing an external queue
+
+If you would like to reference an existing queue from within your application you can use the
+`QueueRef` classes in the target-specific namespaces.
+
+> This is currently only supported for `aws`.
+
+The following example defines a reference to an Amazon SQS queue with a specific ARN and sends a
+message to the queue from the function:
+
+```js
+bring aws;
+
+let outbox = new aws.QueueRef("arn:aws:sqs:us-east-1:111111111111:Outbox");
+
+new cloud.Function(inflight () => {
+ outbox.push("send an email");
+});
+```
+
+This works both when running in the simulator (requires AWS credentials on the developer's machine)
+and when deployed to AWS. When this is deployed to AWS, the AWS Lambda IAM policy will include the
+needed permissions.
+
## Target-specific details
### Simulator (`sim`)
diff --git a/libs/wingsdk/src/core/tree.ts b/libs/wingsdk/src/core/tree.ts
index 806b8895b37..da46c05207c 100644
--- a/libs/wingsdk/src/core/tree.ts
+++ b/libs/wingsdk/src/core/tree.ts
@@ -93,6 +93,7 @@ export interface UIField {
/** The construct path to a cloud.Function */
readonly handler: string;
readonly refreshRate: number | undefined;
+ readonly link?: boolean;
}
/** @internal */
diff --git a/libs/wingsdk/src/shared-aws/permissions.ts b/libs/wingsdk/src/shared-aws/permissions.ts
index 15edc2bb34d..3a281059189 100644
--- a/libs/wingsdk/src/shared-aws/permissions.ts
+++ b/libs/wingsdk/src/shared-aws/permissions.ts
@@ -24,6 +24,12 @@ export function calculateQueuePermissions(
): PolicyStatement[] {
const policies: PolicyStatement[] = [];
+ // this is always needed in order to resolve URL from ARN/name
+ policies.push({
+ actions: ["sqs:GetQueueUrl"],
+ resources: [arn],
+ });
+
if (ops.includes(cloud.QueueInflightMethods.PUSH)) {
policies.push({
actions: ["sqs:SendMessage"],
diff --git a/libs/wingsdk/src/shared-aws/queue.inflight.ts b/libs/wingsdk/src/shared-aws/queue.inflight.ts
index 243dc3aa772..431566da4c7 100644
--- a/libs/wingsdk/src/shared-aws/queue.inflight.ts
+++ b/libs/wingsdk/src/shared-aws/queue.inflight.ts
@@ -6,12 +6,15 @@ import {
ReceiveMessageCommand,
InvalidMessageContents,
DeleteMessageCommand,
+ GetQueueUrlCommand,
} from "@aws-sdk/client-sqs";
import { IQueueClient } from "../cloud";
export class QueueClient implements IQueueClient {
+ private _queueUrl?: string;
+
constructor(
- private readonly queueUrl: string,
+ private readonly _queueUrlOrArn: string,
private readonly client: SQSClient = new SQSClient({})
) {}
@@ -19,10 +22,11 @@ export class QueueClient implements IQueueClient {
if (messages.includes("")) {
throw new Error("Empty messages are not allowed");
}
+
const messagePromises = messages.map(async (message) => {
try {
const command = new SendMessageCommand({
- QueueUrl: this.queueUrl,
+ QueueUrl: await this.queueUrl(),
MessageBody: message,
});
await this.client.send(command);
@@ -41,16 +45,46 @@ export class QueueClient implements IQueueClient {
await Promise.all(messagePromises);
}
+ public async queueUrl(): Promise {
+ if (!this._queueUrl) {
+ // if we have the queue name instead of the url, then we need to resolve it first
+ if (this._queueUrlOrArn.startsWith("https://")) {
+ this._queueUrl = this._queueUrlOrArn;
+ } else {
+ // extract the queue name from its ARN
+ const arnParts = this._queueUrlOrArn.split(":");
+ const queueName = arnParts[arnParts.length - 1].split("/").pop();
+ if (!queueName) {
+ throw new Error(
+ `Unable to extract queue name from ARN: ${this._queueUrlOrArn}`
+ );
+ }
+
+ const command = new GetQueueUrlCommand({ QueueName: queueName });
+ const data = await this.client.send(command);
+ if (!data.QueueUrl) {
+ throw new Error(
+ `Unable to resolve queue URL from SQS queue ARN: ${this._queueUrlOrArn}`
+ );
+ }
+
+ this._queueUrl = data.QueueUrl;
+ }
+ }
+
+ return this._queueUrl;
+ }
+
public async purge(): Promise {
const command = new PurgeQueueCommand({
- QueueUrl: this.queueUrl,
+ QueueUrl: await this.queueUrl(),
});
await this.client.send(command);
}
public async approxSize(): Promise {
const command = new GetQueueAttributesCommand({
- QueueUrl: this.queueUrl,
+ QueueUrl: await this.queueUrl(),
AttributeNames: ["ApproximateNumberOfMessages"],
});
const data = await this.client.send(command);
@@ -59,7 +93,7 @@ export class QueueClient implements IQueueClient {
public async pop(): Promise {
const receiveCommand = new ReceiveMessageCommand({
- QueueUrl: this.queueUrl,
+ QueueUrl: await this.queueUrl(),
MaxNumberOfMessages: 1,
});
const data = await this.client.send(receiveCommand);
@@ -71,7 +105,7 @@ export class QueueClient implements IQueueClient {
if (message.ReceiptHandle) {
const deleteCommand = new DeleteMessageCommand({
- QueueUrl: this.queueUrl,
+ QueueUrl: await this.queueUrl(),
ReceiptHandle: message.ReceiptHandle,
});
await this.client.send(deleteCommand);
diff --git a/libs/wingsdk/src/shared-aws/queue.ts b/libs/wingsdk/src/shared-aws/queue.ts
index 047cae96dd5..a706fad0b3f 100644
--- a/libs/wingsdk/src/shared-aws/queue.ts
+++ b/libs/wingsdk/src/shared-aws/queue.ts
@@ -1,4 +1,14 @@
+import { Construct } from "constructs";
+import { Function } from "./function";
+import { calculateQueuePermissions } from "./permissions";
+import { isValidArn } from "./util";
import { cloud } from "..";
+import { IQueueClient } from "../cloud";
+import { InflightClient } from "../core";
+import { INFLIGHT_SYMBOL } from "../core/types";
+import { Testing } from "../simulator";
+import { IInflightHost, Node, Resource } from "../std";
+import * as ui from "../ui";
/**
* A shared interface for AWS queues.
@@ -20,6 +30,8 @@ export interface IAwsQueue {
readonly queueUrl: string;
}
+const QUEUE_URL_METHOD = "queueUrl";
+
/**
* A helper class for working with AWS queues.
*/
@@ -44,3 +56,126 @@ export class Queue {
);
}
}
+
+/**
+ * A reference to an external SQS queue.
+ *
+ * @inflight `@winglang/sdk.cloud.IQueueClient`
+ */
+export class QueueRef extends Resource {
+ /** @internal */
+ public [INFLIGHT_SYMBOL]?: IQueueClient;
+
+ /**
+ * The ARN of this queue.
+ */
+ public readonly queueArn: string;
+
+ constructor(scope: Construct, id: string, queueArn: string) {
+ super(scope, id);
+
+ if (!isValidArn(queueArn, "sqs")) {
+ throw new Error(`"${queueArn}" is not a valid Amazon SQS ARN`);
+ }
+
+ this.queueArn = queueArn;
+
+ this.addUserInterface();
+ }
+
+ public onLift(host: IInflightHost, ops: string[]): void {
+ // if this is an AWS function, add the necessary IAM permissions
+ const fn = Function.from(host);
+ if (fn) {
+ fn.addPolicyStatements(...calculateQueuePermissions(this.queueArn, ops));
+ }
+
+ host.addEnvironment(this.envName(), this.queueArn);
+ super.onLift(host, ops);
+ }
+
+ /** @internal */
+ public _toInflight(): string {
+ return InflightClient.for(__dirname, __filename, "QueueClient", [
+ `process.env["${this.envName()}"]`,
+ ]);
+ }
+
+ private envName(): string {
+ return `QUEUE_ARN_${this.node.addr.slice(-8)}`;
+ }
+
+ /** @internal */
+ public _supportedOps(): string[] {
+ return [
+ QUEUE_URL_METHOD, // AWS-specific
+ cloud.QueueInflightMethods.PUSH,
+ cloud.QueueInflightMethods.PURGE,
+ cloud.QueueInflightMethods.APPROX_SIZE,
+ cloud.QueueInflightMethods.POP,
+ ];
+ }
+
+ private addUserInterface() {
+ Node.of(this).color = "pink";
+
+ const queueUrlHandler = Testing.makeHandler(
+ `
+ async handle() {
+ try {
+ return await this.queue.queueUrl();
+ } catch (e) {
+ return e.message;
+ }
+ }
+ `,
+ {
+ queue: {
+ obj: this,
+ ops: [QUEUE_URL_METHOD],
+ },
+ }
+ );
+
+ new ui.Field(this, "QueueUrlField", "SQS Queue URL", queueUrlHandler, {
+ link: true,
+ });
+
+ const awsConsoleHandler = Testing.makeHandler(
+ `async handle() {
+ try {
+ const url = await this.queue.queueUrl();
+ const x = new URL(url);
+ const region = x.hostname.split(".")[1];
+ return "https://" + region + ".console.aws.amazon.com/sqs/v3/home?region=" + region + "#/queues/" + encodeURIComponent(url);
+ } catch (e) {
+ return e.message;
+ }
+ }`,
+ {
+ queue: {
+ obj: this,
+ ops: [QUEUE_URL_METHOD],
+ },
+ }
+ );
+
+ new ui.Field(this, "AwsConsoleField", "AWS Console", awsConsoleHandler, {
+ link: true,
+ });
+
+ const queueArnHandler = Testing.makeHandler(
+ `async handle() {
+ return this.queueArn;
+ }`,
+ {
+ queueArn: {
+ obj: this.queueArn,
+ ops: [],
+ },
+ }
+ );
+
+ new ui.Field(this, "QueueArnField", "SQS Queue ARN", queueArnHandler);
+ }
+}
diff --git a/libs/wingsdk/src/shared-aws/util.ts b/libs/wingsdk/src/shared-aws/util.ts
new file mode 100644
index 00000000000..a2302ce6878
--- /dev/null
+++ b/libs/wingsdk/src/shared-aws/util.ts
@@ -0,0 +1,24 @@
+export function isValidArn(arn: string, service: string) {
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
+ const { Token } = require("cdktf");
+
+ // if the ARN is an unresolved token, we can't validate it so assume it's valid
+ if (Token.isUnresolved(arn)) {
+ return true;
+ }
+
+ const parts = arn.split(":");
+ if (parts.length !== 6) {
+ return false;
+ }
+
+ if (parts[0] !== "arn") {
+ return false;
+ }
+
+ if (parts[2] !== service) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/libs/wingsdk/src/ui/field.ts b/libs/wingsdk/src/ui/field.ts
index 6be822a9d5e..4cfb4813026 100644
--- a/libs/wingsdk/src/ui/field.ts
+++ b/libs/wingsdk/src/ui/field.ts
@@ -19,6 +19,13 @@ export interface FieldProps {
* @default - no automatic refresh
*/
readonly refreshRate?: Duration;
+
+ /**
+ * Indicates that this field is a link.
+ *
+ * @default false
+ */
+ readonly link?: boolean;
}
/**
@@ -49,6 +56,7 @@ export class Field extends VisualComponent {
private readonly fn: Function;
private readonly label: string;
private readonly refreshRate: number | undefined;
+ private readonly link: boolean | undefined;
constructor(
scope: Construct,
@@ -62,6 +70,7 @@ export class Field extends VisualComponent {
this.label = label;
this.refreshRate = props.refreshRate?.seconds;
this.fn = new Function(this, "Handler", handler);
+ this.link = props.link;
}
/** @internal */
@@ -71,6 +80,7 @@ export class Field extends VisualComponent {
label: this.label,
handler: this.fn.node.path,
refreshRate: this.refreshRate,
+ link: this.link,
};
}
diff --git a/libs/wingsdk/test/shared-aws/queue.inflight.test.ts b/libs/wingsdk/test/shared-aws/queue.inflight.test.ts
index 085dbc4665d..e2c783d3ab6 100644
--- a/libs/wingsdk/test/shared-aws/queue.inflight.test.ts
+++ b/libs/wingsdk/test/shared-aws/queue.inflight.test.ts
@@ -7,12 +7,14 @@ import {
InvalidMessageContents,
DeleteMessageCommand,
ReceiveMessageCommandOutput,
+ GetQueueUrlCommand,
} from "@aws-sdk/client-sqs";
import { mockClient } from "aws-sdk-client-mock";
import { test, expect, beforeEach } from "vitest";
import { QueueClient } from "../../src/shared-aws/queue.inflight";
import "aws-sdk-client-mock-jest";
+const QUEUE_URL = "https://my-queue-url";
const sqsMock = mockClient(SQSClient);
beforeEach(() => {
@@ -21,7 +23,6 @@ beforeEach(() => {
test("push - happy path", async () => {
// GIVEN
- const QUEUE_URL = "QUEUE_URL";
const MESSAGE = "MESSAGE";
const RESPONSE = {
MessageId: "MESSAGE_ID",
@@ -37,11 +38,11 @@ test("push - happy path", async () => {
// THEN
expect(response).toEqual(undefined);
+ expect(sqsMock).toHaveReceivedCommandTimes(SendMessageCommand, 1);
});
test("push batch - happy path", async () => {
// GIVEN
- const QUEUE_URL = "QUEUE_URL";
const MESSAGES = ["MESSAGE1", "MESSAGE2", "MESSAGE3"];
const RESPONSE = {
MessageId: "MESSAGE_ID",
@@ -72,7 +73,6 @@ test("push batch - happy path", async () => {
test("push - sad path invalid message", async () => {
// GIVEN
- const QUEUE_URL = "QUEUE_URL";
const MESSAGE = "INVALID_MESSAGE";
sqsMock
@@ -95,7 +95,6 @@ test("push - sad path invalid message", async () => {
test("push - sad path empty message", async () => {
// GIVEN
- const QUEUE_URL = "QUEUE_URL";
const MESSAGE = "";
// WHEN
@@ -113,7 +112,6 @@ test("push - sad path empty message", async () => {
test("push - sad path unknown error", async () => {
// GIVEN
- const QUEUE_URL = "QUEUE_URL";
const MESSAGE = "MESSAGE";
sqsMock
@@ -131,7 +129,6 @@ test("push - sad path unknown error", async () => {
test("purge - happy path", async () => {
// GIVEN
- const QUEUE_URL = "QUEUE_URL";
const RESPONSE = {};
sqsMock.on(PurgeQueueCommand, { QueueUrl: QUEUE_URL }).resolves(RESPONSE);
@@ -147,7 +144,6 @@ test("purge - happy path", async () => {
test("approxSize - happy path", async () => {
// GIVEN
const QUEUE_SIZE = 3;
- const QUEUE_URL = "QUEUE_URL";
const GET_QUEUE_ATTRIBUTES_RESPONSE = {
Attributes: { ApproximateNumberOfMessages: QUEUE_SIZE.toString() },
};
@@ -166,7 +162,6 @@ test("approxSize - happy path", async () => {
test("pop - happy path", async () => {
// GIVEN
- const QUEUE_URL = "QUEUE_URL";
const MESSAGE = "MESSAGE";
const ONE_MSG_RESPONSE: ReceiveMessageCommandOutput = {
Messages: [
@@ -198,7 +193,6 @@ test("pop - happy path", async () => {
test("pop - happy path w/o message receipt", async () => {
// GIVEN
- const QUEUE_URL = "QUEUE_URL";
const MESSAGE = "MESSAGE";
const ONE_MSG_RESPONSE: ReceiveMessageCommandOutput = {
Messages: [
@@ -230,7 +224,6 @@ test("pop - happy path w/o message receipt", async () => {
test("pop - happy path w/ no message in the queue", async () => {
// GIVEN
- const QUEUE_URL = "QUEUE_URL";
const NO_MSG_RESPONSE = {};
sqsMock
@@ -248,3 +241,25 @@ test("pop - happy path w/ no message in the queue", async () => {
expect(sqsMock).toHaveReceivedCommandTimes(ReceiveMessageCommand, 2);
expect(sqsMock).toHaveReceivedCommandTimes(DeleteMessageCommand, 0);
});
+
+test("if a queue name is provided, the url is resolved", async () => {
+ const queueName = "MyQueueName";
+
+ // GIVEN
+ sqsMock.on(GetQueueUrlCommand, { QueueName: queueName }).resolves({
+ QueueUrl: "https://my-queue-url",
+ });
+
+ sqsMock
+ .on(SendMessageCommand, {
+ QueueUrl: "https://my-queue-url",
+ MessageBody: "test",
+ })
+ .resolves({});
+
+ const client = new QueueClient(queueName);
+ await client.push("test");
+
+ expect(sqsMock).toHaveReceivedCommandTimes(GetQueueUrlCommand, 1);
+ expect(sqsMock).toHaveReceivedCommandTimes(SendMessageCommand, 1);
+});
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 41f30ea1238..ecf8f57cca0 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
@@ -199,7 +199,7 @@ exports[`function with a queue binding 3`] = `
},
"aws_iam_role_policy": {
"Function_IamRolePolicy_E3B26607": {
- "policy": "{"Version":"2012-10-17","Statement":[{"Action":["sqs:SendMessage"],"Resource":["\${aws_sqs_queue.Queue.arn}"],"Effect":"Allow"}]}",
+ "policy": "{"Version":"2012-10-17","Statement":[{"Action":["sqs:GetQueueUrl"],"Resource":["\${aws_sqs_queue.Queue.arn}"],"Effect":"Allow"},{"Action":["sqs:SendMessage"],"Resource":["\${aws_sqs_queue.Queue.arn}"],"Effect":"Allow"}]}",
"role": "\${aws_iam_role.Function_IamRole_678BE84C.name}",
},
"Queue-SetConsumer0_IamRolePolicy_0299B5AB": {
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 6378f228d9a..28e841f6eb5 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
@@ -1,5 +1,627 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+exports[`QueueRef in an TFAWS app can be used to reference an existing queue 1`] = `
+{
+ "resource": {
+ "aws_cloudwatch_log_group": {
+ "Function_CloudwatchLogGroup_ABDCF4C4": {
+ "name": "/aws/lambda/Function-c852aba6",
+ "retention_in_days": 30,
+ },
+ "QueueRef_AwsConsoleField_Handler_CloudwatchLogGroup_1FAC3ED5": {
+ "name": "/aws/lambda/Handler-c85cfbef",
+ "retention_in_days": 30,
+ },
+ "QueueRef_QueueArnField_Handler_CloudwatchLogGroup_BDFDF613": {
+ "name": "/aws/lambda/Handler-c8c3c90a",
+ "retention_in_days": 30,
+ },
+ "QueueRef_QueueUrlField_Handler_CloudwatchLogGroup_8AB6E3DD": {
+ "name": "/aws/lambda/Handler-c8ec121e",
+ "retention_in_days": 30,
+ },
+ },
+ "aws_iam_role": {
+ "Function_IamRole_678BE84C": {
+ "assume_role_policy": "{"Version":"2012-10-17","Statement":[{"Action":"sts:AssumeRole","Principal":{"Service":"lambda.amazonaws.com"},"Effect":"Allow"}]}",
+ },
+ "QueueRef_AwsConsoleField_Handler_IamRole_0DD0004C": {
+ "assume_role_policy": "{"Version":"2012-10-17","Statement":[{"Action":"sts:AssumeRole","Principal":{"Service":"lambda.amazonaws.com"},"Effect":"Allow"}]}",
+ },
+ "QueueRef_QueueArnField_Handler_IamRole_C121BD5E": {
+ "assume_role_policy": "{"Version":"2012-10-17","Statement":[{"Action":"sts:AssumeRole","Principal":{"Service":"lambda.amazonaws.com"},"Effect":"Allow"}]}",
+ },
+ "QueueRef_QueueUrlField_Handler_IamRole_709A15CB": {
+ "assume_role_policy": "{"Version":"2012-10-17","Statement":[{"Action":"sts:AssumeRole","Principal":{"Service":"lambda.amazonaws.com"},"Effect":"Allow"}]}",
+ },
+ },
+ "aws_iam_role_policy": {
+ "Function_IamRolePolicy_E3B26607": {
+ "policy": "{"Version":"2012-10-17","Statement":[{"Action":["sqs:GetQueueUrl"],"Resource":["arn:aws:sqs:us-west-2:123456789012:MyQueue1234"],"Effect":"Allow"},{"Action":["sqs:SendMessage"],"Resource":["arn:aws:sqs:us-west-2:123456789012:MyQueue1234"],"Effect":"Allow"},{"Action":["sqs:GetQueueAttributes"],"Resource":["arn:aws:sqs:us-west-2:123456789012:MyQueue1234"],"Effect":"Allow"}]}",
+ "role": "\${aws_iam_role.Function_IamRole_678BE84C.name}",
+ },
+ "QueueRef_AwsConsoleField_Handler_IamRolePolicy_184A5238": {
+ "policy": "{"Version":"2012-10-17","Statement":[{"Action":["sqs:GetQueueUrl"],"Resource":["arn:aws:sqs:us-west-2:123456789012:MyQueue1234"],"Effect":"Allow"}]}",
+ "role": "\${aws_iam_role.QueueRef_AwsConsoleField_Handler_IamRole_0DD0004C.name}",
+ },
+ "QueueRef_QueueArnField_Handler_IamRolePolicy_64EE3F4B": {
+ "policy": "{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"none:null","Resource":"*"}]}",
+ "role": "\${aws_iam_role.QueueRef_QueueArnField_Handler_IamRole_C121BD5E.name}",
+ },
+ "QueueRef_QueueUrlField_Handler_IamRolePolicy_A9F2F773": {
+ "policy": "{"Version":"2012-10-17","Statement":[{"Action":["sqs:GetQueueUrl"],"Resource":["arn:aws:sqs:us-west-2:123456789012:MyQueue1234"],"Effect":"Allow"}]}",
+ "role": "\${aws_iam_role.QueueRef_QueueUrlField_Handler_IamRole_709A15CB.name}",
+ },
+ },
+ "aws_iam_role_policy_attachment": {
+ "Function_IamRolePolicyAttachment_CACE1358": {
+ "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+ "role": "\${aws_iam_role.Function_IamRole_678BE84C.name}",
+ },
+ "QueueRef_AwsConsoleField_Handler_IamRolePolicyAttachment_1F892467": {
+ "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+ "role": "\${aws_iam_role.QueueRef_AwsConsoleField_Handler_IamRole_0DD0004C.name}",
+ },
+ "QueueRef_QueueArnField_Handler_IamRolePolicyAttachment_BA9083B2": {
+ "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+ "role": "\${aws_iam_role.QueueRef_QueueArnField_Handler_IamRole_C121BD5E.name}",
+ },
+ "QueueRef_QueueUrlField_Handler_IamRolePolicyAttachment_9FA1E52D": {
+ "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+ "role": "\${aws_iam_role.QueueRef_QueueUrlField_Handler_IamRole_709A15CB.name}",
+ },
+ },
+ "aws_lambda_function": {
+ "Function": {
+ "architectures": [
+ "arm64",
+ ],
+ "environment": {
+ "variables": {
+ "NODE_OPTIONS": "--enable-source-maps",
+ "QUEUE_ARN_23984aca": "arn:aws:sqs:us-west-2:123456789012:MyQueue1234",
+ "WING_FUNCTION_NAME": "Function-c852aba6",
+ },
+ },
+ "function_name": "Function-c852aba6",
+ "handler": "index.handler",
+ "memory_size": 1024,
+ "publish": true,
+ "role": "\${aws_iam_role.Function_IamRole_678BE84C.arn}",
+ "runtime": "nodejs20.x",
+ "s3_bucket": "\${aws_s3_bucket.Code.bucket}",
+ "s3_key": "\${aws_s3_object.Function_S3Object_C62A0C2D.key}",
+ "timeout": 60,
+ "vpc_config": {
+ "security_group_ids": [],
+ "subnet_ids": [],
+ },
+ },
+ "QueueRef_AwsConsoleField_Handler_2D13D4BF": {
+ "architectures": [
+ "arm64",
+ ],
+ "environment": {
+ "variables": {
+ "NODE_OPTIONS": "--enable-source-maps",
+ "QUEUE_ARN_23984aca": "arn:aws:sqs:us-west-2:123456789012:MyQueue1234",
+ "WING_FUNCTION_NAME": "Handler-c85cfbef",
+ },
+ },
+ "function_name": "Handler-c85cfbef",
+ "handler": "index.handler",
+ "memory_size": 1024,
+ "publish": true,
+ "role": "\${aws_iam_role.QueueRef_AwsConsoleField_Handler_IamRole_0DD0004C.arn}",
+ "runtime": "nodejs20.x",
+ "s3_bucket": "\${aws_s3_bucket.Code.bucket}",
+ "s3_key": "\${aws_s3_object.QueueRef_AwsConsoleField_Handler_S3Object_C4237D42.key}",
+ "timeout": 60,
+ "vpc_config": {
+ "security_group_ids": [],
+ "subnet_ids": [],
+ },
+ },
+ "QueueRef_QueueArnField_Handler_0B477E06": {
+ "architectures": [
+ "arm64",
+ ],
+ "environment": {
+ "variables": {
+ "NODE_OPTIONS": "--enable-source-maps",
+ "WING_FUNCTION_NAME": "Handler-c8c3c90a",
+ },
+ },
+ "function_name": "Handler-c8c3c90a",
+ "handler": "index.handler",
+ "memory_size": 1024,
+ "publish": true,
+ "role": "\${aws_iam_role.QueueRef_QueueArnField_Handler_IamRole_C121BD5E.arn}",
+ "runtime": "nodejs20.x",
+ "s3_bucket": "\${aws_s3_bucket.Code.bucket}",
+ "s3_key": "\${aws_s3_object.QueueRef_QueueArnField_Handler_S3Object_6AD15EC0.key}",
+ "timeout": 60,
+ "vpc_config": {
+ "security_group_ids": [],
+ "subnet_ids": [],
+ },
+ },
+ "QueueRef_QueueUrlField_Handler_42A4BE81": {
+ "architectures": [
+ "arm64",
+ ],
+ "environment": {
+ "variables": {
+ "NODE_OPTIONS": "--enable-source-maps",
+ "QUEUE_ARN_23984aca": "arn:aws:sqs:us-west-2:123456789012:MyQueue1234",
+ "WING_FUNCTION_NAME": "Handler-c8ec121e",
+ },
+ },
+ "function_name": "Handler-c8ec121e",
+ "handler": "index.handler",
+ "memory_size": 1024,
+ "publish": true,
+ "role": "\${aws_iam_role.QueueRef_QueueUrlField_Handler_IamRole_709A15CB.arn}",
+ "runtime": "nodejs20.x",
+ "s3_bucket": "\${aws_s3_bucket.Code.bucket}",
+ "s3_key": "\${aws_s3_object.QueueRef_QueueUrlField_Handler_S3Object_225E3B78.key}",
+ "timeout": 60,
+ "vpc_config": {
+ "security_group_ids": [],
+ "subnet_ids": [],
+ },
+ },
+ },
+ "aws_s3_bucket": {
+ "Code": {
+ "bucket_prefix": "code-c84a50b1-",
+ },
+ },
+ "aws_s3_object": {
+ "Function_S3Object_C62A0C2D": {
+ "bucket": "\${aws_s3_bucket.Code.bucket}",
+ "key": "",
+ "source": "