vintasend-aws-sqs 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # VintaSend AWS SQS Queue Services
2
+
3
+ AWS SQS queue services for VintaSend notification dispatch and replication workflows.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install vintasend-aws-sqs
9
+ ```
10
+
11
+ ## What this package provides
12
+
13
+ - `AwsSqsNotificationQueueService`: enqueues notification IDs to a notifications queue
14
+ - `AwsSqsReplicationQueueService`: enqueues notification IDs + backend identifiers to a replication queue
15
+
16
+ Both services send JSON message bodies and are compatible with Lambda SQS triggers.
17
+
18
+ ## Message contracts
19
+
20
+ ### Notification queue message
21
+
22
+ ```json
23
+ {
24
+ "notificationId": "notification-123"
25
+ }
26
+ ```
27
+
28
+ ### Replication queue message
29
+
30
+ ```json
31
+ {
32
+ "notificationId": "notification-123",
33
+ "backendIdentifier": "replica-backend"
34
+ }
35
+ ```
36
+
37
+ ## Basic usage
38
+
39
+ ```typescript
40
+ import {
41
+ AwsSqsNotificationQueueService,
42
+ AwsSqsReplicationQueueService,
43
+ } from 'vintasend-aws-sqs';
44
+
45
+ const notificationQueueService = new AwsSqsNotificationQueueService({
46
+ queueUrl: process.env.NOTIFICATION_QUEUE_URL!,
47
+ sqsClientConfig: { region: process.env.AWS_REGION ?? 'us-east-1' },
48
+ });
49
+
50
+ const replicationQueueService = new AwsSqsReplicationQueueService({
51
+ queueUrl: process.env.REPLICATION_QUEUE_URL!,
52
+ sqsClientConfig: { region: process.env.AWS_REGION ?? 'us-east-1' },
53
+ });
54
+ ```
55
+
56
+ ## Terraform examples
57
+
58
+ This package includes three Terraform examples:
59
+
60
+ - `examples/terraform/notifications-queue.tf`
61
+ - `examples/terraform/replication-queue.tf`
62
+ - `examples/terraform/eventbridge-send-pending-notifications.tf`
63
+
64
+ Each file creates:
65
+ - Primary SQS queue
66
+ - DLQ
67
+ - Redrive policy with configurable retry attempts (`maxReceiveCount`)
68
+ - Lambda event source mapping (`aws_lambda_event_source_mapping`)
69
+ - Useful queue outputs
70
+
71
+ ### 1) Notifications queue + Lambda trigger
72
+
73
+ Use `examples/terraform/notifications-queue.tf` and provide:
74
+
75
+ - Optional `notification_lambda_function_name`: Lambda name that sends notifications
76
+ - Optional queue tuning values (`notification_batch_size`, batching window)
77
+ - Optional retry tuning with `notification_max_receive_count` (default: `5`)
78
+
79
+ ### 2) Replication queue + Lambda trigger
80
+
81
+ Use `examples/terraform/replication-queue.tf` and provide:
82
+
83
+ - Optional `replication_lambda_function_name`: Lambda name that performs replication to the target backend
84
+ - Optional queue tuning values (`replication_batch_size`, batching window)
85
+ - Optional retry tuning with `replication_max_receive_count` (default: `5`)
86
+
87
+ ### 3) EventBridge schedule for periodic `sendPendingNotifications`
88
+
89
+ Use `examples/terraform/eventbridge-send-pending-notifications.tf` and provide:
90
+
91
+ - `send_pending_notifications_lambda_function_name`: Lambda name that calls `sendPendingNotifications`
92
+ - Optional `schedule_expression` (default: `rate(1 minute)`)
93
+ - Optional EventBridge target retry tuning:
94
+ - `eventbridge_target_maximum_retry_attempts` (default: `5`)
95
+ - `eventbridge_target_maximum_event_age_in_seconds` (default: `3600`)
96
+
97
+ ### Example apply flow
98
+
99
+ ```bash
100
+ cd examples/terraform
101
+ terraform init
102
+
103
+ # Create both queues only (no Lambda trigger wiring)
104
+ terraform apply \
105
+ -var="project=my-app" \
106
+ -var="environment=prod"
107
+
108
+ # Create queues and wire notification Lambda trigger
109
+ terraform apply \
110
+ -var="notification_lambda_function_name=vintasend-notification-sender" \
111
+ -var="notification_max_receive_count=8" \
112
+ -var="project=my-app" \
113
+ -var="environment=prod"
114
+
115
+ # Create queues and wire replication Lambda trigger
116
+ terraform apply \
117
+ -var="replication_lambda_function_name=vintasend-replication-worker" \
118
+ -var="replication_max_receive_count=8" \
119
+ -var="project=my-app" \
120
+ -var="environment=prod"
121
+
122
+ # Create EventBridge schedule to trigger sendPendingNotifications periodically
123
+ terraform apply \
124
+ -var="send_pending_notifications_lambda_function_name=vintasend-send-pending-notifications" \
125
+ -var="schedule_expression=rate(5 minutes)" \
126
+ -var="eventbridge_target_maximum_retry_attempts=8" \
127
+ -var="project=my-app" \
128
+ -var="environment=prod"
129
+ ```
130
+
131
+ ## Lambda consumer setup
132
+
133
+ You typically run two Lambda consumers:
134
+
135
+ - Notification sender Lambda
136
+ - Replication worker Lambda
137
+
138
+ Both are triggered by SQS through event source mappings created in Terraform.
139
+
140
+ ### Notification sender Lambda (example)
141
+
142
+ ```typescript
143
+ import type { SQSEvent, SQSBatchResponse } from 'aws-lambda';
144
+ import { getNotificationService } from 'lib/notification-service';
145
+
146
+ interface NotificationQueueMessage {
147
+ notificationId: string;
148
+ }
149
+
150
+ export const handler = async (event: SQSEvent): Promise<SQSBatchResponse> => {
151
+ const failures: { itemIdentifier: string }[] = [];
152
+
153
+ await Promise.all(
154
+ event.Records.map(async (record) => {
155
+ try {
156
+ const payload = JSON.parse(record.body) as NotificationQueueMessage;
157
+
158
+ // Call your VintaSend send operation here
159
+ const vintasend = getNotificationService();
160
+ const notification = vintaSend.getNotification(payload.notificationId);
161
+ await vintasend.send(notification);
162
+ } catch {
163
+ failures.push({ itemIdentifier: record.messageId });
164
+ }
165
+ }),
166
+ );
167
+
168
+ return { batchItemFailures: failures };
169
+ };
170
+ ```
171
+
172
+ ### Replication worker Lambda (example)
173
+
174
+ ```typescript
175
+ import type { SQSEvent, SQSBatchResponse } from 'aws-lambda';
176
+ import { getNotificationService } from 'lib/notification-service';
177
+
178
+ interface ReplicationQueueMessage {
179
+ notificationId: string;
180
+ backendIdentifier: string;
181
+ }
182
+
183
+ export const handler = async (event: SQSEvent): Promise<SQSBatchResponse> => {
184
+ const failures: { itemIdentifier: string }[] = [];
185
+
186
+ await Promise.all(
187
+ event.Records.map(async (record) => {
188
+ try {
189
+ const payload = JSON.parse(record.body) as ReplicationQueueMessage;
190
+
191
+ // Call your replication operation here
192
+ const vintasend = getNotificationService();
193
+ await vintasend.processReplication(payload.notificationId, payload.backendIdentifier);
194
+ } catch {
195
+ failures.push({ itemIdentifier: record.messageId });
196
+ }
197
+ }),
198
+ );
199
+
200
+ return { batchItemFailures: failures };
201
+ };
202
+ ```
203
+
204
+ ### Periodic pending notifications Lambda (EventBridge example)
205
+
206
+ ```typescript
207
+ import type { EventBridgeEvent } from 'aws-lambda';
208
+ import { getNotificationService } from 'lib/notification-service';
209
+
210
+ export const handler = async (
211
+ _event: EventBridgeEvent<string, unknown>,
212
+ ): Promise<{ ok: true }> => {
213
+ const vintasend = getNotificationService();
214
+ await vintasend.sendPendingNotifications();
215
+ return { ok: true };
216
+ };
217
+ ```
218
+
219
+ This Lambda is invoked by EventBridge and should call `sendPendingNotifications()` once per schedule tick.
220
+
221
+ ## Required Lambda IAM permissions
222
+
223
+ Your Lambda execution roles need SQS consumer permissions for their respective queues:
224
+
225
+ - `sqs:ReceiveMessage`
226
+ - `sqs:DeleteMessage`
227
+ - `sqs:ChangeMessageVisibility`
228
+ - `sqs:GetQueueAttributes`
229
+ - `sqs:GetQueueUrl`
230
+
231
+ ## Operational notes
232
+
233
+ - Keep queue `visibility_timeout_seconds` greater than Lambda timeout.
234
+ - Use DLQ monitoring and alarms for failed messages.
235
+ - Use idempotent handlers to safely handle retries.
236
+ - Keep batch size aligned with downstream throughput.
237
+ - Tune retries with `notification_max_receive_count` and `replication_max_receive_count` based on transient failure patterns.
238
+
239
+ ## License
240
+
241
+ MIT
@@ -0,0 +1,15 @@
1
+ import { SQSClient, type SQSClientConfig } from '@aws-sdk/client-sqs';
2
+ import type { BaseNotificationQueueService } from 'vintasend/dist/services/notification-queue-service/base-notification-queue-service';
3
+ import type { BaseNotificationTypeConfig } from 'vintasend/dist/types/notification-type-config';
4
+ export interface AwsSqsNotificationQueueServiceConfig {
5
+ queueUrl: string;
6
+ sqsClient?: SQSClient;
7
+ sqsClientConfig?: SQSClientConfig;
8
+ }
9
+ export declare class AwsSqsNotificationQueueService<Config extends BaseNotificationTypeConfig> implements BaseNotificationQueueService<Config> {
10
+ private config;
11
+ private sqsClient;
12
+ constructor(config: AwsSqsNotificationQueueServiceConfig);
13
+ enqueueNotification(notificationId: Config['NotificationIdType']): Promise<void>;
14
+ }
15
+ //# sourceMappingURL=aws-sqs-notification-queue-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aws-sqs-notification-queue-service.d.ts","sourceRoot":"","sources":["../src/aws-sqs-notification-queue-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,oFAAoF,CAAC;AACvI,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAEhG,MAAM,WAAW,oCAAoC;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,eAAe,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,qBAAa,8BAA8B,CAC1C,MAAM,SAAS,0BAA0B,CACxC,YAAW,4BAA4B,CAAC,MAAM,CAAC;IAIpC,OAAO,CAAC,MAAM;IAF1B,OAAO,CAAC,SAAS,CAAY;gBAET,MAAM,EAAE,oCAAoC;IAQ1D,mBAAmB,CAAC,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAUtF"}
@@ -0,0 +1,19 @@
1
+ import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs';
2
+ export class AwsSqsNotificationQueueService {
3
+ constructor(config) {
4
+ this.config = config;
5
+ this.sqsClient =
6
+ config.sqsClient ??
7
+ new SQSClient({
8
+ ...config.sqsClientConfig,
9
+ });
10
+ }
11
+ async enqueueNotification(notificationId) {
12
+ await this.sqsClient.send(new SendMessageCommand({
13
+ QueueUrl: this.config.queueUrl,
14
+ MessageBody: JSON.stringify({
15
+ notificationId,
16
+ }),
17
+ }));
18
+ }
19
+ }
@@ -0,0 +1,15 @@
1
+ import { SQSClient, type SQSClientConfig } from '@aws-sdk/client-sqs';
2
+ import type { BaseNotificationReplicationQueueService } from 'vintasend/dist/services/notification-queue-service/base-notification-replication-queue-service';
3
+ import type { BaseNotificationTypeConfig } from 'vintasend/dist/types/notification-type-config';
4
+ export interface AwsSqsReplicationQueueServiceConfig {
5
+ queueUrl: string;
6
+ sqsClient?: SQSClient;
7
+ sqsClientConfig?: SQSClientConfig;
8
+ }
9
+ export declare class AwsSqsReplicationQueueService<Config extends BaseNotificationTypeConfig> implements BaseNotificationReplicationQueueService<Config> {
10
+ private config;
11
+ private sqsClient;
12
+ constructor(config: AwsSqsReplicationQueueServiceConfig);
13
+ enqueueReplication(notificationId: Config['NotificationIdType'], backendIdentifier: string): Promise<void>;
14
+ }
15
+ //# sourceMappingURL=aws-sqs-replication-queue-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aws-sqs-replication-queue-service.d.ts","sourceRoot":"","sources":["../src/aws-sqs-replication-queue-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,gGAAgG,CAAC;AAC9J,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAEhG,MAAM,WAAW,mCAAmC;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,eAAe,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,qBAAa,6BAA6B,CACzC,MAAM,SAAS,0BAA0B,CACxC,YAAW,uCAAuC,CAAC,MAAM,CAAC;IAI/C,OAAO,CAAC,MAAM;IAF1B,OAAO,CAAC,SAAS,CAAY;gBAET,MAAM,EAAE,mCAAmC;IAQzD,kBAAkB,CACvB,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,EAC5C,iBAAiB,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;CAWhB"}
@@ -0,0 +1,20 @@
1
+ import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs';
2
+ export class AwsSqsReplicationQueueService {
3
+ constructor(config) {
4
+ this.config = config;
5
+ this.sqsClient =
6
+ config.sqsClient ??
7
+ new SQSClient({
8
+ ...config.sqsClientConfig,
9
+ });
10
+ }
11
+ async enqueueReplication(notificationId, backendIdentifier) {
12
+ await this.sqsClient.send(new SendMessageCommand({
13
+ QueueUrl: this.config.queueUrl,
14
+ MessageBody: JSON.stringify({
15
+ notificationId,
16
+ backendIdentifier,
17
+ }),
18
+ }));
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ export { AwsSqsNotificationQueueService, type AwsSqsNotificationQueueServiceConfig, } from './aws-sqs-notification-queue-service';
2
+ export { AwsSqsReplicationQueueService, type AwsSqsReplicationQueueServiceConfig, } from './aws-sqs-replication-queue-service';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,8BAA8B,EAC9B,KAAK,oCAAoC,GACzC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACN,6BAA6B,EAC7B,KAAK,mCAAmC,GACxC,MAAM,qCAAqC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { AwsSqsNotificationQueueService, } from './aws-sqs-notification-queue-service';
2
+ export { AwsSqsReplicationQueueService, } from './aws-sqs-replication-queue-service';
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "vintasend-aws-sqs",
3
+ "version": "0.9.0",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "prepublishOnly": "npm run build",
9
+ "test": "jest",
10
+ "test:watch": "jest --watch",
11
+ "test:coverage": "jest --coverage"
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "author": "Hugo Bessa",
17
+ "license": "MIT",
18
+ "dependencies": {
19
+ "@aws-sdk/client-sqs": "^3.1000.0",
20
+ "vintasend": "^0.9.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/jest": "^30.0.0",
24
+ "jest": "^30.2.0",
25
+ "ts-jest": "^29.4.6",
26
+ "typescript": "^5.9.3"
27
+ }
28
+ }