Skip to content

Commit b09701c

Browse files
committed
feat(plugin): added new SQS plugin
1 parent 392c3e0 commit b09701c

28 files changed

+2760
-361
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ functions:
121121
```
122122

123123
- `files`
124-
include additionnal files into the package.
124+
include additional files into the package.
125125

126126
```yaml
127127
functions:
@@ -163,6 +163,7 @@ See [defineConfig](resources/defineConfig.md) for advanced configuration.
163163

164164
- [AWS Local S3](resources/s3.md)
165165
- [AWS Local SNS](resources/sns.md)
166+
- [AWS Local SQS](resources/sqs.md)
166167
- [DynamoDB Local Streams](https://github.com/Inqnuam/serverless-aws-lambda-ddb-streams)
167168
- [Jest](https://github.com/Inqnuam/serverless-aws-lambda-jest)
168169
- [Vitest](https://github.com/Inqnuam/serverless-aws-lambda-vitest)

build.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const buildIndex = bundle.bind(null, {
4343
"./src/lib/runtime/worker.ts",
4444
"./src/lambda/router.ts",
4545
"./src/plugins/sns/index.ts",
46+
"./src/plugins/sqs/index.ts",
4647
"./src/plugins/s3/index.ts",
4748
"./src/lambda/body-parser.ts",
4849
],

package.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "serverless-aws-lambda",
3-
"version": "4.3.1",
3+
"version": "4.4.0",
44
"description": "AWS Application Load Balancer and API Gateway - Lambda dev tool for Serverless. Allows Express synthax in handlers. Supports packaging, local invoking and offline real ALB and APG lambda server mocking.",
55
"author": "Inqnuam",
66
"license": "MIT",
@@ -38,21 +38,25 @@
3838
"types": "./dist/plugins/sns/index.d.ts",
3939
"require": "./dist/plugins/sns/index.js"
4040
},
41+
"./sqs": {
42+
"types": "./dist/plugins/sqs/index.d.ts",
43+
"require": "./dist/plugins/sqs/index.js"
44+
},
4145
"./s3": {
4246
"types": "./dist/plugins/s3/index.d.ts",
4347
"require": "./dist/plugins/s3/index.js"
4448
}
4549
},
4650
"dependencies": {
47-
"@types/serverless": "^3.12.10",
51+
"@types/serverless": "^3.12.11",
4852
"archiver": "^5.3.1",
49-
"esbuild": "0.17.11",
53+
"esbuild": "0.17.12",
5054
"serve-static": "^1.15.0"
5155
},
5256
"devDependencies": {
5357
"@types/archiver": "^5.3.1",
5458
"@types/node": "^14.14.31",
55-
"@types/serve-static": "^1.15.0",
59+
"@types/serve-static": "^1.15.1",
5660
"typescript": "^4.9.5"
5761
},
5862
"keywords": [
@@ -62,9 +66,13 @@
6266
"local",
6367
"apg",
6468
"alb",
69+
"elb",
6570
"lambda",
6671
"sns",
72+
"sqs",
6773
"s3",
74+
"stream",
75+
"dynamodb",
6876
"invoke",
6977
"bundle",
7078
"esbuild",

resources/sqs.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
## AWS Local SQS
2+
3+
### Description
4+
5+
Plugin for serverless-aws-lambda to trigger locally your sqs event defined lambdas automatically.
6+
Local Queues are created based on your serverless.yml.
7+
To define default and/or override Queue attributes see [Plugin configs](../src/plugins/sqs/types.ts).
8+
9+
### Installation
10+
11+
Import the plugin inside your defineConfig.
12+
13+
```js
14+
// config.js
15+
const { defineConfig } = require("serverless-aws-lambda/defineConfig");
16+
const { sqsPlugin } = require("serverless-aws-lambda/sqs");
17+
18+
module.exports = defineConfig({
19+
plugins: [sqsPlugin(config)],
20+
});
21+
```
22+
23+
### Supported Requests
24+
25+
supports both AWS SDK and CLI requests.
26+
27+
✅ supported
28+
🌕 planned
29+
❌ not planned
30+
31+
- 🌕 AddPermission
32+
- ✅ ChangeMessageVisibility
33+
- ✅ ChangeMessageVisibilityBatch
34+
- 🌕 CreateQueue
35+
- ✅ DeleteMessage
36+
- ✅ DeleteMessageBatch
37+
- ✅ DeleteQueue
38+
- 🌕 GetQueueAttributes
39+
- 🌕 GetQueueUrl
40+
- 🌕 ListDeadLetterSourceQueues
41+
- ✅ ListQueues
42+
- ✅ ListQueueTags
43+
- ✅ PurgeQueue
44+
- ✅ ReceiveMessage
45+
- 🌕 RemovePermission
46+
- ✅ SendMessage
47+
- ✅ SendMessageBatch
48+
- 🌕 SetQueueAttributes
49+
- ✅ TagQueue
50+
- ✅ UntagQueue
51+
52+
### Examples
53+
54+
AWS CLI
55+
56+
```bash
57+
aws sqs --region eu-west-1 --endpoint http://localhost:9999/@sqs list-queues
58+
```
59+
60+
AWS SDK
61+
62+
```js
63+
import { SQSClient, ListQueuesCommand } from "@aws-sdk/client-sqs";
64+
65+
const client = new SQSClient({
66+
region: "eu-west-1",
67+
endpoint: `http://localhost:9999/@sqs`,
68+
});
69+
70+
const queues = await client.send(new ListQueuesCommand({}));
71+
console.log(queues);
72+
```

src/defineConfig.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ export interface ClientConfigParams {
3333
config: Config;
3434
options: Options;
3535
serverless: Serverless;
36+
resources: {
37+
ddb: {};
38+
kinesis: {};
39+
sns: {};
40+
sqs: {};
41+
};
3642
}
3743

3844
export interface SlsAwsLambdaPlugin {
@@ -52,7 +58,7 @@ export interface SlsAwsLambdaPlugin {
5258
*/
5359
method?: HttpMethod | HttpMethod[];
5460
filter: string | RegExp;
55-
callback: (this: ClientConfigParams, req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
61+
callback: (this: ClientConfigParams, req: IncomingMessage, res: ServerResponse) => Promise<any | void> | any | void;
5662
}[];
5763
};
5864
}
@@ -112,7 +118,7 @@ function defineConfig(options: Options) {
112118
}
113119
return async function config(
114120
this: ClientConfigParams,
115-
{ stop, lambdas, isDeploying, isPackaging, setEnv, stage, port, esbuild, serverless }: ClientConfigParams
121+
{ stop, lambdas, isDeploying, isPackaging, setEnv, stage, port, esbuild, serverless, resources }: ClientConfigParams
116122
): Promise<Omit<Config, "config" | "options">> {
117123
let config: Config = {
118124
esbuild: options.esbuild ?? {},
@@ -135,6 +141,7 @@ function defineConfig(options: Options) {
135141
serverless,
136142
options,
137143
config,
144+
resources,
138145
};
139146
if (options.plugins) {
140147
config.offline!.onReady = async (port, ip) => {

src/index.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ class ServerlessAwsLambda extends Daemon {
3939
nodeVersion: number | boolean | string | undefined = false;
4040
invokeName?: string;
4141
afterDeployCallbacks: (() => void | Promise<void>)[] = [];
42+
resources: {
43+
ddb: {};
44+
kinesis: {};
45+
sns: {};
46+
sqs: {};
47+
};
4248
constructor(serverless: any, options: any) {
4349
super({ debug: process.env.SLS_DEBUG == "*" });
4450

@@ -117,6 +123,8 @@ class ServerlessAwsLambda extends Daemon {
117123
"before:invoke:local:invoke": this.invokeLocal.bind(this),
118124
"after:aws:deploy:finalize:cleanup": this.afterDeploy.bind(this),
119125
};
126+
127+
this.resources = getResources(this.serverless);
120128
}
121129

122130
async invokeLocal() {
@@ -289,7 +297,7 @@ class ServerlessAwsLambda extends Daemon {
289297
functionsNames = functionsNames.filter((x) => x == this.invokeName);
290298
}
291299
const defaultRuntime = this.serverless.service.provider.runtime;
292-
const resources = getResources(this.serverless);
300+
293301
// @ts-ignore
294302
const Outputs = this.serverless.service.resources?.Outputs;
295303
const lambdas = functionsNames.reduce((accum: any[], funcName: string) => {
@@ -319,6 +327,7 @@ class ServerlessAwsLambda extends Daemon {
319327
timeout: lambda.timeout ?? this.runtimeConfig.timeout ?? DEFAULT_LAMBDA_TIMEOUT,
320328
endpoints: [],
321329
sns: [],
330+
sqs: [],
322331
ddb: [],
323332
s3: [],
324333
kinesis: [],
@@ -350,7 +359,7 @@ class ServerlessAwsLambda extends Daemon {
350359
};
351360

352361
// @ts-ignore
353-
lambdaDef.onError = parseDestination(lambda.onError, Outputs, resources);
362+
lambdaDef.onError = parseDestination(lambda.onError, Outputs, this.resources);
354363

355364
if (lambdaDef.onError?.kind == "lambda") {
356365
log.YELLOW("Dead-Letter queue could only be a SNS or SQS service");
@@ -359,9 +368,9 @@ class ServerlessAwsLambda extends Daemon {
359368
//@ts-ignore
360369
if (lambda.destinations && typeof lambda.destinations == "object") {
361370
//@ts-ignore
362-
lambdaDef.onFailure = parseDestination(lambda.destinations.onFailure, Outputs, resources);
371+
lambdaDef.onFailure = parseDestination(lambda.destinations.onFailure, Outputs, this.resources);
363372
//@ts-ignore
364-
lambdaDef.onSuccess = parseDestination(lambda.destinations.onSuccess, Outputs, resources);
373+
lambdaDef.onSuccess = parseDestination(lambda.destinations.onSuccess, Outputs, this.resources);
365374
}
366375

367376
lambdaDef.onInvoke = (callback: (event: any, info?: any) => void) => {
@@ -379,9 +388,10 @@ class ServerlessAwsLambda extends Daemon {
379388
lambdaDef.environment.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = lambdaDef.memorySize;
380389

381390
if (lambda.events.length) {
382-
const { endpoints, sns, ddb, s3, kinesis } = parseEvents(lambda.events, Outputs, resources);
391+
const { endpoints, sns, sqs, ddb, s3, kinesis } = parseEvents(lambda.events, Outputs, this.resources);
383392
lambdaDef.endpoints = endpoints;
384393
lambdaDef.sns = sns;
394+
lambdaDef.sqs = sqs;
385395
lambdaDef.ddb = ddb;
386396
lambdaDef.s3 = s3;
387397
lambdaDef.kinesis = kinesis;
@@ -479,6 +489,7 @@ class ServerlessAwsLambda extends Daemon {
479489
stage: this.options.stage ?? this.serverless.service.provider.stage ?? "dev",
480490
esbuild: esbuild,
481491
serverless: this.serverless,
492+
resources: this.resources,
482493
};
483494
let exportedObject: any = {};
484495

src/lib/parseEvents/ddbStream.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,7 @@ export const parseDdbStreamDefinitions = (Outputs: any, resources: any, event: a
8989
parsedEvent.batchWindow = val.batchWindow;
9090
}
9191

92-
if (
93-
!isNaN(val.maximumRecordAgeInSeconds) &&
94-
parsedEvent.maximumRecordAgeInSeconds >= StreamProps.minRecordAge &&
95-
parsedEvent.maximumRecordAgeInSeconds <= StreamProps.maxRecordAge
96-
) {
92+
if (!isNaN(val.maximumRecordAgeInSeconds) && val.maximumRecordAgeInSeconds >= StreamProps.minRecordAge && val.maximumRecordAgeInSeconds <= StreamProps.maxRecordAge) {
9793
parsedEvent.maximumRecordAgeInSeconds = val.maximumRecordAgeInSeconds;
9894
}
9995

src/lib/parseEvents/getResources.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,71 @@ export const getResources = (serverless: any) => {
3434
StreamModeDetails,
3535
};
3636
} else if (value.Type == "AWS::SNS::Topic" && value.Properties) {
37-
const { TopicName } = value.Properties;
37+
const { TopicName, ContentBasedDeduplication, DisplayName, FifoTopic } = value.Properties;
3838
accum.sns[key] = {
3939
TopicName,
40+
ContentBasedDeduplication,
41+
DisplayName,
42+
FifoTopic,
4043
};
4144
} else if (value.Type == "AWS::SQS::Queue" && value.Properties) {
42-
const { QueueName } = value.Properties;
45+
const {
46+
QueueName,
47+
ContentBasedDeduplication,
48+
DeduplicationScope,
49+
DelaySeconds,
50+
FifoQueue,
51+
FifoThroughputLimit,
52+
KmsDataKeyReusePeriodSeconds,
53+
KmsMasterKeyId,
54+
MaximumMessageSize,
55+
MessageRetentionPeriod,
56+
ReceiveMessageWaitTimeSeconds,
57+
RedriveAllowPolicy,
58+
RedrivePolicy,
59+
VisibilityTimeout,
60+
Tags,
61+
} = value.Properties;
62+
4363
accum.sqs[key] = {
4464
QueueName,
65+
ContentBasedDeduplication,
66+
DeduplicationScope,
67+
DelaySeconds,
68+
FifoQueue,
69+
FifoThroughputLimit,
70+
KmsDataKeyReusePeriodSeconds,
71+
KmsMasterKeyId,
72+
MaximumMessageSize,
73+
MessageRetentionPeriod,
74+
ReceiveMessageWaitTimeSeconds,
75+
VisibilityTimeout,
76+
RedriveAllowPolicy,
77+
Tags: {},
4578
};
79+
80+
if (RedrivePolicy && typeof RedrivePolicy.deadLetterTargetArn == "string") {
81+
if (RedrivePolicy.deadLetterTargetArn.startsWith("arn:aws:sqs:")) {
82+
const components = RedrivePolicy.deadLetterTargetArn.split(":");
83+
const name = components[components.length - 1];
84+
85+
accum.sqs[key].RedrivePolicy = {
86+
name,
87+
deadLetterTargetArn: RedrivePolicy.deadLetterTargetArn,
88+
maxReceiveCount: !isNaN(RedrivePolicy.maxReceiveCount) ? Number(RedrivePolicy.maxReceiveCount) : 10,
89+
};
90+
} else {
91+
console.log("deadLetterTargetArn must be a string like arn:aws:sqs:...");
92+
}
93+
}
94+
95+
if (Array.isArray(Tags)) {
96+
Tags.forEach((x) => {
97+
if (typeof x.Key == "string" && x.Value) {
98+
accum.sqs[key].Tags[x.Key] = x.Value;
99+
}
100+
});
101+
}
46102
}
47103

48104
return accum;

src/lib/parseEvents/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,22 @@ import { parseSns } from "./sns";
55
import { parseDdbStreamDefinitions } from "./ddbStream";
66
import { parseS3 } from "./s3";
77
import { parseKinesis } from "./kinesis";
8+
import { parseSqs } from "./sqs";
89

910
const supportedServices: IDestination["kind"][] = ["lambda", "sns", "sqs"];
1011
type arn = [string, string, IDestination["kind"], string, string, string, string];
1112

1213
export const parseEvents = (events: any[], Outputs: any, resources: any) => {
1314
const endpoints: LambdaEndpoint[] = [];
1415
const sns: any[] = [];
16+
const sqs: any[] = [];
1517
const ddb: any[] = [];
1618
const s3: any[] = [];
1719
const kinesis: any[] = [];
1820
for (const event of events) {
1921
const slsEvent = parseEndpoints(event);
2022
const snsEvent = parseSns(Outputs, resources, event);
23+
const sqsEvent = parseSqs(Outputs, resources, event);
2124
const ddbStream = parseDdbStreamDefinitions(Outputs, resources, event);
2225
const s3Event = parseS3(event);
2326
const kinesisStream = parseKinesis(event, Outputs, resources);
@@ -28,6 +31,10 @@ export const parseEvents = (events: any[], Outputs: any, resources: any) => {
2831
sns.push(snsEvent);
2932
}
3033

34+
if (sqsEvent) {
35+
sqs.push(sqsEvent);
36+
}
37+
3138
if (ddbStream) {
3239
ddb.push(ddbStream);
3340
}
@@ -40,7 +47,7 @@ export const parseEvents = (events: any[], Outputs: any, resources: any) => {
4047
}
4148
}
4249

43-
return { ddb, endpoints, s3, sns, kinesis };
50+
return { ddb, endpoints, s3, sns, sqs, kinesis };
4451
};
4552

4653
export const parseDestination = (destination: any, Outputs: any, resources: any): IDestination | undefined => {

0 commit comments

Comments
 (0)