@geekmidas/events 0.0.1

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.
Files changed (211) hide show
  1. package/README.md +473 -0
  2. package/dist/BasicConnection-BNi6_olg.mjs +42 -0
  3. package/dist/BasicConnection-BNi6_olg.mjs.map +1 -0
  4. package/dist/BasicConnection-C4_Y0nIj.d.mts +26 -0
  5. package/dist/BasicConnection-CddnULuT.d.cts +26 -0
  6. package/dist/BasicConnection-CzfMubv_.cjs +48 -0
  7. package/dist/BasicConnection-CzfMubv_.cjs.map +1 -0
  8. package/dist/BasicPublisher-BzfzHIfb.cjs +20 -0
  9. package/dist/BasicPublisher-BzfzHIfb.cjs.map +1 -0
  10. package/dist/BasicPublisher-Cg3SxIVR.mjs +14 -0
  11. package/dist/BasicPublisher-Cg3SxIVR.mjs.map +1 -0
  12. package/dist/BasicPublisher-DCBxaTwC.d.mts +12 -0
  13. package/dist/BasicPublisher-V5hELHG9.d.cts +12 -0
  14. package/dist/BasicSubscriber-BB3aKeph.d.mts +12 -0
  15. package/dist/BasicSubscriber-BEv5N21f.mjs +18 -0
  16. package/dist/BasicSubscriber-BEv5N21f.mjs.map +1 -0
  17. package/dist/BasicSubscriber-DOwgFw5j.d.cts +12 -0
  18. package/dist/BasicSubscriber-DyTKOixe.cjs +24 -0
  19. package/dist/BasicSubscriber-DyTKOixe.cjs.map +1 -0
  20. package/dist/EventConnection-ClLMoIUU.mjs +36 -0
  21. package/dist/EventConnection-ClLMoIUU.mjs.map +1 -0
  22. package/dist/EventConnection-DZhn1Msl.d.cts +13 -0
  23. package/dist/EventConnection-DeXfLGom.d.mts +13 -0
  24. package/dist/EventConnection-F_N81SXu.cjs +41 -0
  25. package/dist/EventConnection-F_N81SXu.cjs.map +1 -0
  26. package/dist/EventConnection.cjs +5 -0
  27. package/dist/EventConnection.d.cts +3 -0
  28. package/dist/EventConnection.d.mts +3 -0
  29. package/dist/EventConnection.mjs +4 -0
  30. package/dist/Publisher-C3m8JFXm.d.mts +19 -0
  31. package/dist/Publisher-DS0aOh8r.mjs +65 -0
  32. package/dist/Publisher-DS0aOh8r.mjs.map +1 -0
  33. package/dist/Publisher-ceu6OxCj.cjs +70 -0
  34. package/dist/Publisher-ceu6OxCj.cjs.map +1 -0
  35. package/dist/Publisher-nXNVjL0e.d.cts +19 -0
  36. package/dist/Publisher.cjs +4 -0
  37. package/dist/Publisher.d.cts +3 -0
  38. package/dist/Publisher.d.mts +3 -0
  39. package/dist/Publisher.mjs +4 -0
  40. package/dist/RabbitMQConnection-7cWp_-_Q.d.cts +43 -0
  41. package/dist/RabbitMQConnection-BN_GybOv.cjs +113 -0
  42. package/dist/RabbitMQConnection-BN_GybOv.cjs.map +1 -0
  43. package/dist/RabbitMQConnection-C2HC7J7K.d.mts +43 -0
  44. package/dist/RabbitMQConnection-eVOVS4Dl.mjs +107 -0
  45. package/dist/RabbitMQConnection-eVOVS4Dl.mjs.map +1 -0
  46. package/dist/RabbitMQPublisher-BZX0edgL.mjs +39 -0
  47. package/dist/RabbitMQPublisher-BZX0edgL.mjs.map +1 -0
  48. package/dist/RabbitMQPublisher-CSCn6gMA.cjs +45 -0
  49. package/dist/RabbitMQPublisher-CSCn6gMA.cjs.map +1 -0
  50. package/dist/RabbitMQPublisher-CpcbgrXj.d.cts +23 -0
  51. package/dist/RabbitMQPublisher-DDZi15Ro.d.mts +23 -0
  52. package/dist/RabbitMQSubscriber-B5I-hBaT.mjs +57 -0
  53. package/dist/RabbitMQSubscriber-B5I-hBaT.mjs.map +1 -0
  54. package/dist/RabbitMQSubscriber-BJ9PTGny.d.cts +25 -0
  55. package/dist/RabbitMQSubscriber-CIvIw8MG.d.mts +25 -0
  56. package/dist/RabbitMQSubscriber-Cj7qLIi6.cjs +63 -0
  57. package/dist/RabbitMQSubscriber-Cj7qLIi6.cjs.map +1 -0
  58. package/dist/SNSConnection-CAImaMM2.d.mts +40 -0
  59. package/dist/SNSConnection-Cqbr7-Ho.d.cts +40 -0
  60. package/dist/SNSConnection-D7wCf9Si.mjs +70 -0
  61. package/dist/SNSConnection-D7wCf9Si.mjs.map +1 -0
  62. package/dist/SNSConnection-DVsC_pbB.cjs +76 -0
  63. package/dist/SNSConnection-DVsC_pbB.cjs.map +1 -0
  64. package/dist/SNSPublisher-BZ6N3JI9.cjs +48 -0
  65. package/dist/SNSPublisher-BZ6N3JI9.cjs.map +1 -0
  66. package/dist/SNSPublisher-BfWjzNiB.mjs +42 -0
  67. package/dist/SNSPublisher-BfWjzNiB.mjs.map +1 -0
  68. package/dist/SNSPublisher-Dkhb4LU-.d.mts +21 -0
  69. package/dist/SNSPublisher-RVZFa7kL.d.cts +21 -0
  70. package/dist/SNSSubscriber-B31JmkAE.d.mts +54 -0
  71. package/dist/SNSSubscriber-C9YJWa7m.mjs +143 -0
  72. package/dist/SNSSubscriber-C9YJWa7m.mjs.map +1 -0
  73. package/dist/SNSSubscriber-UE0ia4XM.cjs +149 -0
  74. package/dist/SNSSubscriber-UE0ia4XM.cjs.map +1 -0
  75. package/dist/SNSSubscriber-k2UwKUxu.d.cts +54 -0
  76. package/dist/SQSConnection-BwNjUPr4.cjs +76 -0
  77. package/dist/SQSConnection-BwNjUPr4.cjs.map +1 -0
  78. package/dist/SQSConnection-C5WFqZW7.d.mts +40 -0
  79. package/dist/SQSConnection-Eleqqre3.mjs +70 -0
  80. package/dist/SQSConnection-Eleqqre3.mjs.map +1 -0
  81. package/dist/SQSConnection-geav2Rsn.d.cts +40 -0
  82. package/dist/SQSPublisher-CdsayOLV.d.mts +23 -0
  83. package/dist/SQSPublisher-DG9o4ME3.mjs +57 -0
  84. package/dist/SQSPublisher-DG9o4ME3.mjs.map +1 -0
  85. package/dist/SQSPublisher-Dq5353oZ.cjs +63 -0
  86. package/dist/SQSPublisher-Dq5353oZ.cjs.map +1 -0
  87. package/dist/SQSPublisher-Utb_FTe5.d.cts +23 -0
  88. package/dist/SQSSubscriber-B6iL1EnX.d.cts +43 -0
  89. package/dist/SQSSubscriber-CD66P7hH.mjs +121 -0
  90. package/dist/SQSSubscriber-CD66P7hH.mjs.map +1 -0
  91. package/dist/SQSSubscriber-DBeE4XAR.d.mts +43 -0
  92. package/dist/SQSSubscriber-DZDNgLE5.cjs +127 -0
  93. package/dist/SQSSubscriber-DZDNgLE5.cjs.map +1 -0
  94. package/dist/Subscriber-Bd9hb_jp.mjs +74 -0
  95. package/dist/Subscriber-Bd9hb_jp.mjs.map +1 -0
  96. package/dist/Subscriber-Cff9EIpv.d.mts +19 -0
  97. package/dist/Subscriber-CpAwcUzb.d.cts +19 -0
  98. package/dist/Subscriber-saVOWuMq.cjs +79 -0
  99. package/dist/Subscriber-saVOWuMq.cjs.map +1 -0
  100. package/dist/Subscriber.cjs +4 -0
  101. package/dist/Subscriber.d.cts +3 -0
  102. package/dist/Subscriber.d.mts +3 -0
  103. package/dist/Subscriber.mjs +4 -0
  104. package/dist/basic/BasicConnection.cjs +4 -0
  105. package/dist/basic/BasicConnection.d.cts +3 -0
  106. package/dist/basic/BasicConnection.d.mts +3 -0
  107. package/dist/basic/BasicConnection.mjs +4 -0
  108. package/dist/basic/BasicPublisher.cjs +3 -0
  109. package/dist/basic/BasicPublisher.d.cts +4 -0
  110. package/dist/basic/BasicPublisher.d.mts +4 -0
  111. package/dist/basic/BasicPublisher.mjs +3 -0
  112. package/dist/basic/BasicSubscriber.cjs +3 -0
  113. package/dist/basic/BasicSubscriber.d.cts +4 -0
  114. package/dist/basic/BasicSubscriber.d.mts +4 -0
  115. package/dist/basic/BasicSubscriber.mjs +3 -0
  116. package/dist/basic/index.cjs +8 -0
  117. package/dist/basic/index.d.cts +5 -0
  118. package/dist/basic/index.d.mts +5 -0
  119. package/dist/basic/index.mjs +6 -0
  120. package/dist/chunk-CUT6urMc.cjs +30 -0
  121. package/dist/index.cjs +9 -0
  122. package/dist/index.d.cts +5 -0
  123. package/dist/index.d.mts +5 -0
  124. package/dist/index.mjs +6 -0
  125. package/dist/rabbitmq/RabbitMQConnection.cjs +4 -0
  126. package/dist/rabbitmq/RabbitMQConnection.d.cts +3 -0
  127. package/dist/rabbitmq/RabbitMQConnection.d.mts +3 -0
  128. package/dist/rabbitmq/RabbitMQConnection.mjs +4 -0
  129. package/dist/rabbitmq/RabbitMQPublisher.cjs +3 -0
  130. package/dist/rabbitmq/RabbitMQPublisher.d.cts +4 -0
  131. package/dist/rabbitmq/RabbitMQPublisher.d.mts +4 -0
  132. package/dist/rabbitmq/RabbitMQPublisher.mjs +3 -0
  133. package/dist/rabbitmq/RabbitMQSubscriber.cjs +3 -0
  134. package/dist/rabbitmq/RabbitMQSubscriber.d.cts +4 -0
  135. package/dist/rabbitmq/RabbitMQSubscriber.d.mts +4 -0
  136. package/dist/rabbitmq/RabbitMQSubscriber.mjs +3 -0
  137. package/dist/rabbitmq/index.cjs +8 -0
  138. package/dist/rabbitmq/index.d.cts +5 -0
  139. package/dist/rabbitmq/index.d.mts +5 -0
  140. package/dist/rabbitmq/index.mjs +6 -0
  141. package/dist/sns/SNSConnection.cjs +4 -0
  142. package/dist/sns/SNSConnection.d.cts +3 -0
  143. package/dist/sns/SNSConnection.d.mts +3 -0
  144. package/dist/sns/SNSConnection.mjs +4 -0
  145. package/dist/sns/SNSPublisher.cjs +3 -0
  146. package/dist/sns/SNSPublisher.d.cts +4 -0
  147. package/dist/sns/SNSPublisher.d.mts +4 -0
  148. package/dist/sns/SNSPublisher.mjs +3 -0
  149. package/dist/sns/SNSSubscriber.cjs +6 -0
  150. package/dist/sns/SNSSubscriber.d.cts +4 -0
  151. package/dist/sns/SNSSubscriber.d.mts +4 -0
  152. package/dist/sns/SNSSubscriber.mjs +6 -0
  153. package/dist/sns/index.cjs +10 -0
  154. package/dist/sns/index.d.cts +5 -0
  155. package/dist/sns/index.d.mts +5 -0
  156. package/dist/sns/index.mjs +8 -0
  157. package/dist/sqs/SQSConnection.cjs +4 -0
  158. package/dist/sqs/SQSConnection.d.cts +3 -0
  159. package/dist/sqs/SQSConnection.d.mts +3 -0
  160. package/dist/sqs/SQSConnection.mjs +4 -0
  161. package/dist/sqs/SQSPublisher.cjs +3 -0
  162. package/dist/sqs/SQSPublisher.d.cts +4 -0
  163. package/dist/sqs/SQSPublisher.d.mts +4 -0
  164. package/dist/sqs/SQSPublisher.mjs +3 -0
  165. package/dist/sqs/SQSSubscriber.cjs +3 -0
  166. package/dist/sqs/SQSSubscriber.d.cts +4 -0
  167. package/dist/sqs/SQSSubscriber.d.mts +4 -0
  168. package/dist/sqs/SQSSubscriber.mjs +3 -0
  169. package/dist/sqs/index.cjs +8 -0
  170. package/dist/sqs/index.d.cts +5 -0
  171. package/dist/sqs/index.d.mts +5 -0
  172. package/dist/sqs/index.mjs +6 -0
  173. package/dist/types-B63qHVOG.mjs +17 -0
  174. package/dist/types-B63qHVOG.mjs.map +1 -0
  175. package/dist/types-CvSMEO5z.cjs +23 -0
  176. package/dist/types-CvSMEO5z.cjs.map +1 -0
  177. package/dist/types-D0g-o6wX.d.mts +65 -0
  178. package/dist/types-DEGUg5BA.d.cts +65 -0
  179. package/dist/types.cjs +3 -0
  180. package/dist/types.d.cts +2 -0
  181. package/dist/types.d.mts +2 -0
  182. package/dist/types.mjs +3 -0
  183. package/package.json +49 -0
  184. package/src/EventConnection.ts +39 -0
  185. package/src/Publisher.ts +87 -0
  186. package/src/Subscriber.ts +102 -0
  187. package/src/__tests__/Subscriber.spec.ts +41 -0
  188. package/src/basic/BasicConnection.ts +47 -0
  189. package/src/basic/BasicPublisher.ts +15 -0
  190. package/src/basic/BasicSubscriber.ts +24 -0
  191. package/src/basic/__tests__/BasicSubscriber.spec.ts +166 -0
  192. package/src/basic/index.ts +3 -0
  193. package/src/index.ts +25 -0
  194. package/src/rabbitmq/RabbitMQConnection.ts +148 -0
  195. package/src/rabbitmq/RabbitMQPublisher.ts +67 -0
  196. package/src/rabbitmq/RabbitMQSubscriber.ts +113 -0
  197. package/src/rabbitmq/__tests__/RabbitMQPublisher.spec.ts +378 -0
  198. package/src/rabbitmq/__tests__/RabbitMQSubscriber.spec.ts +292 -0
  199. package/src/rabbitmq/index.ts +6 -0
  200. package/src/sns/SNSConnection.ts +98 -0
  201. package/src/sns/SNSPublisher.ts +64 -0
  202. package/src/sns/SNSSubscriber.ts +222 -0
  203. package/src/sns/__tests__/SNS-SQS-Integration.spec.ts +389 -0
  204. package/src/sns/index.ts +6 -0
  205. package/src/sqs/SQSConnection.ts +97 -0
  206. package/src/sqs/SQSPublisher.ts +105 -0
  207. package/src/sqs/SQSSubscriber.ts +189 -0
  208. package/src/sqs/__tests__/SQSPublisher.spec.ts +189 -0
  209. package/src/sqs/index.ts +6 -0
  210. package/src/types.ts +76 -0
  211. package/tsdown.config.ts +5 -0
@@ -0,0 +1,189 @@
1
+ import {
2
+ DeleteMessageCommand,
3
+ type Message,
4
+ ReceiveMessageCommand,
5
+ } from '@aws-sdk/client-sqs';
6
+ import type { EventSubscriber, PublishableMessage } from '../types';
7
+ import type { SQSConnection } from './SQSConnection';
8
+
9
+ export interface SQSSubscriberOptions {
10
+ waitTimeSeconds?: number; // SQS long polling (default: 20)
11
+ maxMessages?: number; // Max messages per poll (default: 10)
12
+ expectedTopicArn?: string; // Optional: verify messages are from specific SNS topic
13
+ }
14
+
15
+ /**
16
+ * SQS Subscriber - receives messages from an SQS queue
17
+ *
18
+ * This subscriber can receive:
19
+ * 1. Direct SQS messages
20
+ * 2. SNS messages that were pushed to the queue (SNS → SQS pattern)
21
+ *
22
+ * When receiving SNS messages, it:
23
+ * - Automatically detects and parses the SNS wrapper format
24
+ * - Filters by TopicArn (if expectedTopicArn is provided)
25
+ * - Filters by message type
26
+ * - Processes the actual message payload
27
+ */
28
+ export class SQSSubscriber<TMessage extends PublishableMessage<string, any>>
29
+ implements EventSubscriber<TMessage>
30
+ {
31
+ private polling = false;
32
+ private messageTypes: Set<TMessage['type']> = new Set();
33
+ private listener?: (payload: TMessage) => Promise<void>;
34
+ private options: Required<SQSSubscriberOptions>;
35
+
36
+ constructor(
37
+ private connection: SQSConnection,
38
+ options: SQSSubscriberOptions = {},
39
+ ) {
40
+ this.options = {
41
+ waitTimeSeconds: options.waitTimeSeconds ?? 20,
42
+ maxMessages: options.maxMessages ?? 10,
43
+ expectedTopicArn: options.expectedTopicArn ?? '',
44
+ };
45
+ }
46
+
47
+ async subscribe(
48
+ messages: TMessage['type'][],
49
+ listener: (payload: TMessage) => Promise<void>,
50
+ ): Promise<void> {
51
+ this.messageTypes = new Set(messages);
52
+ this.listener = listener;
53
+
54
+ if (!this.connection.isConnected()) {
55
+ await this.connection.connect();
56
+ }
57
+
58
+ this.polling = true;
59
+ this.poll();
60
+ }
61
+
62
+ stop(): void {
63
+ this.polling = false;
64
+ }
65
+
66
+ private async poll(): Promise<void> {
67
+ while (this.polling) {
68
+ try {
69
+ const command = new ReceiveMessageCommand({
70
+ QueueUrl: this.connection.queueUrl,
71
+ MaxNumberOfMessages: this.options.maxMessages,
72
+ WaitTimeSeconds: this.options.waitTimeSeconds,
73
+ MessageAttributeNames: ['All'],
74
+ });
75
+
76
+ const response = await this.connection.sqsClient.send(command);
77
+
78
+ if (response.Messages && response.Messages.length > 0) {
79
+ await this.processMessages(response.Messages);
80
+ }
81
+ } catch (error) {
82
+ console.error('Error receiving messages from SQS:', error);
83
+ await this.sleep(1000);
84
+ }
85
+ }
86
+ }
87
+
88
+ private async processMessages(messages: Message[]): Promise<void> {
89
+ for (const message of messages) {
90
+ try {
91
+ if (!message.Body) continue;
92
+
93
+ // Try to parse as SNS message first
94
+ const parsedMessage = this.parseMessage(message.Body);
95
+
96
+ if (!parsedMessage) {
97
+ // Not a valid message format
98
+ await this.deleteMessage(message.ReceiptHandle!);
99
+ continue;
100
+ }
101
+
102
+ const { payload, messageType, topicArn } = parsedMessage;
103
+
104
+ // If expectedTopicArn is set, verify the message is from that topic
105
+ if (this.options.expectedTopicArn && topicArn) {
106
+ if (topicArn !== this.options.expectedTopicArn) {
107
+ console.warn(
108
+ `Received message from unexpected topic: ${topicArn}, expected: ${this.options.expectedTopicArn}`,
109
+ );
110
+ await this.deleteMessage(message.ReceiptHandle!);
111
+ continue;
112
+ }
113
+ }
114
+
115
+ // Check if we're subscribed to this message type
116
+ if (
117
+ messageType &&
118
+ this.messageTypes.has(messageType as TMessage['type'])
119
+ ) {
120
+ // Call listener
121
+ if (this.listener) {
122
+ await this.listener(payload as TMessage);
123
+ }
124
+
125
+ // Delete message after successful processing
126
+ await this.deleteMessage(message.ReceiptHandle!);
127
+ } else {
128
+ // Delete messages we're not subscribed to
129
+ await this.deleteMessage(message.ReceiptHandle!);
130
+ }
131
+ } catch (error) {
132
+ console.error('Error processing message:', error);
133
+ // Message will become visible again
134
+ }
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Parse message body - handles both direct SQS messages and SNS-wrapped messages
140
+ */
141
+ private parseMessage(body: string): {
142
+ payload: unknown;
143
+ messageType: string | undefined;
144
+ topicArn?: string;
145
+ } | null {
146
+ try {
147
+ const parsed = JSON.parse(body);
148
+
149
+ // Check if this is an SNS notification wrapper
150
+ if (parsed.Type === 'Notification' && parsed.Message) {
151
+ // This is an SNS message
152
+ const snsMessage = JSON.parse(parsed.Message);
153
+ const messageType =
154
+ parsed.MessageAttributes?.type?.Value || snsMessage.type;
155
+
156
+ return {
157
+ payload: snsMessage,
158
+ messageType,
159
+ topicArn: parsed.TopicArn,
160
+ };
161
+ }
162
+
163
+ // This is a direct SQS message
164
+ return {
165
+ payload: parsed,
166
+ messageType: parsed.type,
167
+ };
168
+ } catch (error) {
169
+ console.error('Failed to parse message:', error);
170
+ return null;
171
+ }
172
+ }
173
+
174
+ private async deleteMessage(receiptHandle: string): Promise<void> {
175
+ try {
176
+ const command = new DeleteMessageCommand({
177
+ QueueUrl: this.connection.queueUrl,
178
+ ReceiptHandle: receiptHandle,
179
+ });
180
+ await this.connection.sqsClient.send(command);
181
+ } catch (error) {
182
+ console.error('Error deleting message:', error);
183
+ }
184
+ }
185
+
186
+ private sleep(ms: number): Promise<void> {
187
+ return new Promise((resolve) => setTimeout(resolve, ms));
188
+ }
189
+ }
@@ -0,0 +1,189 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { PublishableMessage } from '../../types';
3
+ import { SQSConnection } from '../SQSConnection';
4
+ import { SQSPublisher } from '../SQSPublisher';
5
+
6
+ type TestMessage = PublishableMessage<'user.created' | 'user.updated', any>;
7
+
8
+ const TEST_QUEUE_URL =
9
+ 'https://sqs.us-east-1.amazonaws.com/123456789012/test-queue';
10
+
11
+ describe('SQSPublisher', () => {
12
+ describe('constructor', () => {
13
+ it('should create instance with queue URL', () => {
14
+ const connection = new SQSConnection({
15
+ queueUrl: TEST_QUEUE_URL,
16
+ });
17
+ const publisher = new SQSPublisher<TestMessage>(connection);
18
+
19
+ expect(publisher).toBeDefined();
20
+ });
21
+
22
+ it('should create instance with region', () => {
23
+ const connection = new SQSConnection({
24
+ queueUrl: TEST_QUEUE_URL,
25
+ region: 'us-east-1',
26
+ });
27
+ const publisher = new SQSPublisher<TestMessage>(connection);
28
+
29
+ expect(publisher).toBeDefined();
30
+ });
31
+
32
+ it('should create instance with credentials', () => {
33
+ const connection = new SQSConnection({
34
+ queueUrl: TEST_QUEUE_URL,
35
+ region: 'us-east-1',
36
+ credentials: {
37
+ accessKeyId: 'test-key',
38
+ secretAccessKey: 'test-secret',
39
+ },
40
+ });
41
+ const publisher = new SQSPublisher<TestMessage>(connection);
42
+
43
+ expect(publisher).toBeDefined();
44
+ });
45
+
46
+ it('should create instance with custom batch size', () => {
47
+ const connection = new SQSConnection({
48
+ queueUrl: TEST_QUEUE_URL,
49
+ });
50
+ const publisher = new SQSPublisher<TestMessage>(connection, {
51
+ maxBatchSize: 5,
52
+ });
53
+
54
+ expect(publisher).toBeDefined();
55
+ });
56
+
57
+ it('should create instance with custom endpoint', () => {
58
+ const connection = new SQSConnection({
59
+ queueUrl: TEST_QUEUE_URL,
60
+ endpoint: 'http://localhost:4566',
61
+ });
62
+ const publisher = new SQSPublisher<TestMessage>(connection);
63
+
64
+ expect(publisher).toBeDefined();
65
+ });
66
+
67
+ it('should default maxBatchSize to 10', () => {
68
+ const connection = new SQSConnection({
69
+ queueUrl: TEST_QUEUE_URL,
70
+ });
71
+ const publisher = new SQSPublisher<TestMessage>(connection);
72
+
73
+ // Access private options through any to test default
74
+ expect(publisher.options.maxBatchSize).toBe(10);
75
+ });
76
+ });
77
+
78
+ describe('fromConnectionString', () => {
79
+ it('should parse basic connection string', async () => {
80
+ const publisher = await SQSPublisher.fromConnectionString<TestMessage>(
81
+ `sqs://?queueUrl=${encodeURIComponent(TEST_QUEUE_URL)}`,
82
+ );
83
+
84
+ expect(publisher).toBeDefined();
85
+ });
86
+
87
+ it('should parse connection string with region', async () => {
88
+ const publisher = await SQSPublisher.fromConnectionString<TestMessage>(
89
+ `sqs://?queueUrl=${encodeURIComponent(TEST_QUEUE_URL)}&region=us-west-2`,
90
+ );
91
+
92
+ expect(publisher).toBeDefined();
93
+ expect(publisher.connection.config.region).toBe('us-west-2');
94
+ });
95
+
96
+ it('should parse connection string with credentials', async () => {
97
+ const publisher = await SQSPublisher.fromConnectionString<TestMessage>(
98
+ `sqs://?queueUrl=${encodeURIComponent(TEST_QUEUE_URL)}&accessKeyId=test-key&secretAccessKey=test-secret`,
99
+ );
100
+
101
+ expect(publisher).toBeDefined();
102
+ expect(publisher.connection.config.credentials).toEqual({
103
+ accessKeyId: 'test-key',
104
+ secretAccessKey: 'test-secret',
105
+ sessionToken: undefined,
106
+ });
107
+ });
108
+
109
+ it('should parse connection string with session token', async () => {
110
+ const publisher = await SQSPublisher.fromConnectionString<TestMessage>(
111
+ `sqs://?queueUrl=${encodeURIComponent(TEST_QUEUE_URL)}&accessKeyId=test-key&secretAccessKey=test-secret&sessionToken=test-token`,
112
+ );
113
+
114
+ expect(publisher).toBeDefined();
115
+ expect(publisher.connection.config.credentials).toEqual({
116
+ accessKeyId: 'test-key',
117
+ secretAccessKey: 'test-secret',
118
+ sessionToken: 'test-token',
119
+ });
120
+ });
121
+
122
+ it('should parse connection string with maxBatchSize', async () => {
123
+ const publisher = await SQSPublisher.fromConnectionString<TestMessage>(
124
+ `sqs://?queueUrl=${encodeURIComponent(TEST_QUEUE_URL)}&maxBatchSize=5`,
125
+ );
126
+
127
+ expect(publisher).toBeDefined();
128
+ expect(publisher.options.maxBatchSize).toBe(5);
129
+ });
130
+
131
+ it('should parse connection string with endpoint', async () => {
132
+ const publisher = await SQSPublisher.fromConnectionString<TestMessage>(
133
+ `sqs://?queueUrl=${encodeURIComponent(TEST_QUEUE_URL)}&endpoint=${encodeURIComponent('http://localhost:4566')}`,
134
+ );
135
+
136
+ expect(publisher).toBeDefined();
137
+ expect(publisher.connection.config.endpoint).toBe(
138
+ 'http://localhost:4566',
139
+ );
140
+ });
141
+
142
+ it('should parse connection string with all parameters', async () => {
143
+ const publisher = await SQSPublisher.fromConnectionString<TestMessage>(
144
+ `sqs://?queueUrl=${encodeURIComponent(TEST_QUEUE_URL)}&region=us-west-2&endpoint=${encodeURIComponent('http://localhost:4566')}&maxBatchSize=5&accessKeyId=test&secretAccessKey=secret`,
145
+ );
146
+
147
+ expect(publisher).toBeDefined();
148
+ expect(publisher.connection.config.region).toBe('us-west-2');
149
+ expect(publisher.connection.config.endpoint).toBe(
150
+ 'http://localhost:4566',
151
+ );
152
+ expect(publisher.options.maxBatchSize).toBe(5);
153
+ expect(publisher.connection.config.credentials).toEqual({
154
+ accessKeyId: 'test',
155
+ secretAccessKey: 'secret',
156
+ sessionToken: undefined,
157
+ });
158
+ });
159
+
160
+ it('should throw error if queueUrl is missing', async () => {
161
+ await expect(
162
+ SQSPublisher.fromConnectionString<TestMessage>(
163
+ 'sqs://?region=us-east-1',
164
+ ),
165
+ ).rejects.toThrow('queueUrl parameter is required');
166
+ });
167
+ });
168
+
169
+ describe('close', () => {
170
+ it('should close without error', async () => {
171
+ const connection = new SQSConnection({
172
+ queueUrl: TEST_QUEUE_URL,
173
+ });
174
+ const publisher = new SQSPublisher<TestMessage>(connection);
175
+
176
+ await expect(publisher.close()).resolves.toBeUndefined();
177
+ });
178
+
179
+ it('should allow multiple close calls', async () => {
180
+ const connection = new SQSConnection({
181
+ queueUrl: TEST_QUEUE_URL,
182
+ });
183
+ const publisher = new SQSPublisher<TestMessage>(connection);
184
+
185
+ await publisher.close();
186
+ await expect(publisher.close()).resolves.toBeUndefined();
187
+ });
188
+ });
189
+ });
@@ -0,0 +1,6 @@
1
+ export { SQSConnection } from './SQSConnection';
2
+ export type { SQSConnectionConfig } from './SQSConnection';
3
+ export { SQSPublisher } from './SQSPublisher';
4
+ export type { SQSPublisherOptions } from './SQSPublisher';
5
+ export { SQSSubscriber } from './SQSSubscriber';
6
+ export type { SQSSubscriberOptions } from './SQSSubscriber';
package/src/types.ts ADDED
@@ -0,0 +1,76 @@
1
+ import type { InferStandardSchema } from '@geekmidas/schema';
2
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
3
+
4
+ /**
5
+ * Represents a publishable event message with a type and payload.
6
+ *
7
+ * @template TType - The event type/name (e.g., 'user.created')
8
+ * @template TPayload - The event payload data
9
+ */
10
+ export type PublishableMessage<TType extends string, TPayload> = {
11
+ type: TType;
12
+ payload: TPayload;
13
+ };
14
+
15
+ /**
16
+ * Event publisher/subscriber types
17
+ */
18
+ export enum EventPublisherType {
19
+ Basic = 'basic',
20
+ EventBridge = 'eventbridge',
21
+ SQS = 'sqs',
22
+ SNS = 'sns',
23
+ Kafka = 'kafka',
24
+ RabbitMQ = 'rabbitmq',
25
+ }
26
+
27
+ /**
28
+ * Base interface for event connections
29
+ * Connections manage the underlying transport (RabbitMQ channel, SQS client, etc.)
30
+ */
31
+ export interface EventConnection {
32
+ readonly type: EventPublisherType;
33
+ connect(): Promise<void>;
34
+ close(): Promise<void>;
35
+ isConnected(): boolean;
36
+ }
37
+
38
+ /**
39
+ * Interface for event publishers that handle the actual publishing of events.
40
+ * Implementations can send to EventBridge, SQS, Kafka, or any other event system.
41
+ *
42
+ * @template TMessage - The union type of all publishable messages
43
+ */
44
+ export type EventPublisher<TMessage extends PublishableMessage<string, any>> = {
45
+ publish: (message: TMessage[]) => Promise<void>;
46
+ };
47
+
48
+ /**
49
+ * Interface for event subscribers that handle receiving and processing events.
50
+ *
51
+ * @template TMessage - The union type of all publishable messages
52
+ */
53
+ export type EventSubscriber<TMessage extends PublishableMessage<string, any>> =
54
+ {
55
+ subscribe: (
56
+ messages: TMessage['type'][],
57
+ listener: (payload: TMessage) => Promise<void>,
58
+ ) => Promise<void>;
59
+ };
60
+ /**
61
+ * Utility type to extract the message from EventPublisher
62
+ */
63
+ export type ExtractPublisherMessage<T> = T extends EventPublisher<infer M>
64
+ ? M
65
+ : never;
66
+
67
+ export type MappedEvent<
68
+ T extends EventPublisher<any> | undefined,
69
+ OutSchema extends StandardSchemaV1 | undefined = undefined,
70
+ > = {
71
+ type: ExtractPublisherMessage<T>['type'];
72
+ payload: (
73
+ ctx: InferStandardSchema<OutSchema>,
74
+ ) => ExtractPublisherMessage<T>['payload'];
75
+ when?: (ctx: InferStandardSchema<OutSchema>) => boolean;
76
+ };
@@ -0,0 +1,5 @@
1
+ import { defineConfig } from 'tsdown';
2
+
3
+ export default defineConfig({
4
+ external: ['amqplib', '@aws-sdk/client-sqs', '@aws-sdk/client-sns'],
5
+ });