@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.
- package/README.md +473 -0
- package/dist/BasicConnection-BNi6_olg.mjs +42 -0
- package/dist/BasicConnection-BNi6_olg.mjs.map +1 -0
- package/dist/BasicConnection-C4_Y0nIj.d.mts +26 -0
- package/dist/BasicConnection-CddnULuT.d.cts +26 -0
- package/dist/BasicConnection-CzfMubv_.cjs +48 -0
- package/dist/BasicConnection-CzfMubv_.cjs.map +1 -0
- package/dist/BasicPublisher-BzfzHIfb.cjs +20 -0
- package/dist/BasicPublisher-BzfzHIfb.cjs.map +1 -0
- package/dist/BasicPublisher-Cg3SxIVR.mjs +14 -0
- package/dist/BasicPublisher-Cg3SxIVR.mjs.map +1 -0
- package/dist/BasicPublisher-DCBxaTwC.d.mts +12 -0
- package/dist/BasicPublisher-V5hELHG9.d.cts +12 -0
- package/dist/BasicSubscriber-BB3aKeph.d.mts +12 -0
- package/dist/BasicSubscriber-BEv5N21f.mjs +18 -0
- package/dist/BasicSubscriber-BEv5N21f.mjs.map +1 -0
- package/dist/BasicSubscriber-DOwgFw5j.d.cts +12 -0
- package/dist/BasicSubscriber-DyTKOixe.cjs +24 -0
- package/dist/BasicSubscriber-DyTKOixe.cjs.map +1 -0
- package/dist/EventConnection-ClLMoIUU.mjs +36 -0
- package/dist/EventConnection-ClLMoIUU.mjs.map +1 -0
- package/dist/EventConnection-DZhn1Msl.d.cts +13 -0
- package/dist/EventConnection-DeXfLGom.d.mts +13 -0
- package/dist/EventConnection-F_N81SXu.cjs +41 -0
- package/dist/EventConnection-F_N81SXu.cjs.map +1 -0
- package/dist/EventConnection.cjs +5 -0
- package/dist/EventConnection.d.cts +3 -0
- package/dist/EventConnection.d.mts +3 -0
- package/dist/EventConnection.mjs +4 -0
- package/dist/Publisher-C3m8JFXm.d.mts +19 -0
- package/dist/Publisher-DS0aOh8r.mjs +65 -0
- package/dist/Publisher-DS0aOh8r.mjs.map +1 -0
- package/dist/Publisher-ceu6OxCj.cjs +70 -0
- package/dist/Publisher-ceu6OxCj.cjs.map +1 -0
- package/dist/Publisher-nXNVjL0e.d.cts +19 -0
- package/dist/Publisher.cjs +4 -0
- package/dist/Publisher.d.cts +3 -0
- package/dist/Publisher.d.mts +3 -0
- package/dist/Publisher.mjs +4 -0
- package/dist/RabbitMQConnection-7cWp_-_Q.d.cts +43 -0
- package/dist/RabbitMQConnection-BN_GybOv.cjs +113 -0
- package/dist/RabbitMQConnection-BN_GybOv.cjs.map +1 -0
- package/dist/RabbitMQConnection-C2HC7J7K.d.mts +43 -0
- package/dist/RabbitMQConnection-eVOVS4Dl.mjs +107 -0
- package/dist/RabbitMQConnection-eVOVS4Dl.mjs.map +1 -0
- package/dist/RabbitMQPublisher-BZX0edgL.mjs +39 -0
- package/dist/RabbitMQPublisher-BZX0edgL.mjs.map +1 -0
- package/dist/RabbitMQPublisher-CSCn6gMA.cjs +45 -0
- package/dist/RabbitMQPublisher-CSCn6gMA.cjs.map +1 -0
- package/dist/RabbitMQPublisher-CpcbgrXj.d.cts +23 -0
- package/dist/RabbitMQPublisher-DDZi15Ro.d.mts +23 -0
- package/dist/RabbitMQSubscriber-B5I-hBaT.mjs +57 -0
- package/dist/RabbitMQSubscriber-B5I-hBaT.mjs.map +1 -0
- package/dist/RabbitMQSubscriber-BJ9PTGny.d.cts +25 -0
- package/dist/RabbitMQSubscriber-CIvIw8MG.d.mts +25 -0
- package/dist/RabbitMQSubscriber-Cj7qLIi6.cjs +63 -0
- package/dist/RabbitMQSubscriber-Cj7qLIi6.cjs.map +1 -0
- package/dist/SNSConnection-CAImaMM2.d.mts +40 -0
- package/dist/SNSConnection-Cqbr7-Ho.d.cts +40 -0
- package/dist/SNSConnection-D7wCf9Si.mjs +70 -0
- package/dist/SNSConnection-D7wCf9Si.mjs.map +1 -0
- package/dist/SNSConnection-DVsC_pbB.cjs +76 -0
- package/dist/SNSConnection-DVsC_pbB.cjs.map +1 -0
- package/dist/SNSPublisher-BZ6N3JI9.cjs +48 -0
- package/dist/SNSPublisher-BZ6N3JI9.cjs.map +1 -0
- package/dist/SNSPublisher-BfWjzNiB.mjs +42 -0
- package/dist/SNSPublisher-BfWjzNiB.mjs.map +1 -0
- package/dist/SNSPublisher-Dkhb4LU-.d.mts +21 -0
- package/dist/SNSPublisher-RVZFa7kL.d.cts +21 -0
- package/dist/SNSSubscriber-B31JmkAE.d.mts +54 -0
- package/dist/SNSSubscriber-C9YJWa7m.mjs +143 -0
- package/dist/SNSSubscriber-C9YJWa7m.mjs.map +1 -0
- package/dist/SNSSubscriber-UE0ia4XM.cjs +149 -0
- package/dist/SNSSubscriber-UE0ia4XM.cjs.map +1 -0
- package/dist/SNSSubscriber-k2UwKUxu.d.cts +54 -0
- package/dist/SQSConnection-BwNjUPr4.cjs +76 -0
- package/dist/SQSConnection-BwNjUPr4.cjs.map +1 -0
- package/dist/SQSConnection-C5WFqZW7.d.mts +40 -0
- package/dist/SQSConnection-Eleqqre3.mjs +70 -0
- package/dist/SQSConnection-Eleqqre3.mjs.map +1 -0
- package/dist/SQSConnection-geav2Rsn.d.cts +40 -0
- package/dist/SQSPublisher-CdsayOLV.d.mts +23 -0
- package/dist/SQSPublisher-DG9o4ME3.mjs +57 -0
- package/dist/SQSPublisher-DG9o4ME3.mjs.map +1 -0
- package/dist/SQSPublisher-Dq5353oZ.cjs +63 -0
- package/dist/SQSPublisher-Dq5353oZ.cjs.map +1 -0
- package/dist/SQSPublisher-Utb_FTe5.d.cts +23 -0
- package/dist/SQSSubscriber-B6iL1EnX.d.cts +43 -0
- package/dist/SQSSubscriber-CD66P7hH.mjs +121 -0
- package/dist/SQSSubscriber-CD66P7hH.mjs.map +1 -0
- package/dist/SQSSubscriber-DBeE4XAR.d.mts +43 -0
- package/dist/SQSSubscriber-DZDNgLE5.cjs +127 -0
- package/dist/SQSSubscriber-DZDNgLE5.cjs.map +1 -0
- package/dist/Subscriber-Bd9hb_jp.mjs +74 -0
- package/dist/Subscriber-Bd9hb_jp.mjs.map +1 -0
- package/dist/Subscriber-Cff9EIpv.d.mts +19 -0
- package/dist/Subscriber-CpAwcUzb.d.cts +19 -0
- package/dist/Subscriber-saVOWuMq.cjs +79 -0
- package/dist/Subscriber-saVOWuMq.cjs.map +1 -0
- package/dist/Subscriber.cjs +4 -0
- package/dist/Subscriber.d.cts +3 -0
- package/dist/Subscriber.d.mts +3 -0
- package/dist/Subscriber.mjs +4 -0
- package/dist/basic/BasicConnection.cjs +4 -0
- package/dist/basic/BasicConnection.d.cts +3 -0
- package/dist/basic/BasicConnection.d.mts +3 -0
- package/dist/basic/BasicConnection.mjs +4 -0
- package/dist/basic/BasicPublisher.cjs +3 -0
- package/dist/basic/BasicPublisher.d.cts +4 -0
- package/dist/basic/BasicPublisher.d.mts +4 -0
- package/dist/basic/BasicPublisher.mjs +3 -0
- package/dist/basic/BasicSubscriber.cjs +3 -0
- package/dist/basic/BasicSubscriber.d.cts +4 -0
- package/dist/basic/BasicSubscriber.d.mts +4 -0
- package/dist/basic/BasicSubscriber.mjs +3 -0
- package/dist/basic/index.cjs +8 -0
- package/dist/basic/index.d.cts +5 -0
- package/dist/basic/index.d.mts +5 -0
- package/dist/basic/index.mjs +6 -0
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/index.cjs +9 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +6 -0
- package/dist/rabbitmq/RabbitMQConnection.cjs +4 -0
- package/dist/rabbitmq/RabbitMQConnection.d.cts +3 -0
- package/dist/rabbitmq/RabbitMQConnection.d.mts +3 -0
- package/dist/rabbitmq/RabbitMQConnection.mjs +4 -0
- package/dist/rabbitmq/RabbitMQPublisher.cjs +3 -0
- package/dist/rabbitmq/RabbitMQPublisher.d.cts +4 -0
- package/dist/rabbitmq/RabbitMQPublisher.d.mts +4 -0
- package/dist/rabbitmq/RabbitMQPublisher.mjs +3 -0
- package/dist/rabbitmq/RabbitMQSubscriber.cjs +3 -0
- package/dist/rabbitmq/RabbitMQSubscriber.d.cts +4 -0
- package/dist/rabbitmq/RabbitMQSubscriber.d.mts +4 -0
- package/dist/rabbitmq/RabbitMQSubscriber.mjs +3 -0
- package/dist/rabbitmq/index.cjs +8 -0
- package/dist/rabbitmq/index.d.cts +5 -0
- package/dist/rabbitmq/index.d.mts +5 -0
- package/dist/rabbitmq/index.mjs +6 -0
- package/dist/sns/SNSConnection.cjs +4 -0
- package/dist/sns/SNSConnection.d.cts +3 -0
- package/dist/sns/SNSConnection.d.mts +3 -0
- package/dist/sns/SNSConnection.mjs +4 -0
- package/dist/sns/SNSPublisher.cjs +3 -0
- package/dist/sns/SNSPublisher.d.cts +4 -0
- package/dist/sns/SNSPublisher.d.mts +4 -0
- package/dist/sns/SNSPublisher.mjs +3 -0
- package/dist/sns/SNSSubscriber.cjs +6 -0
- package/dist/sns/SNSSubscriber.d.cts +4 -0
- package/dist/sns/SNSSubscriber.d.mts +4 -0
- package/dist/sns/SNSSubscriber.mjs +6 -0
- package/dist/sns/index.cjs +10 -0
- package/dist/sns/index.d.cts +5 -0
- package/dist/sns/index.d.mts +5 -0
- package/dist/sns/index.mjs +8 -0
- package/dist/sqs/SQSConnection.cjs +4 -0
- package/dist/sqs/SQSConnection.d.cts +3 -0
- package/dist/sqs/SQSConnection.d.mts +3 -0
- package/dist/sqs/SQSConnection.mjs +4 -0
- package/dist/sqs/SQSPublisher.cjs +3 -0
- package/dist/sqs/SQSPublisher.d.cts +4 -0
- package/dist/sqs/SQSPublisher.d.mts +4 -0
- package/dist/sqs/SQSPublisher.mjs +3 -0
- package/dist/sqs/SQSSubscriber.cjs +3 -0
- package/dist/sqs/SQSSubscriber.d.cts +4 -0
- package/dist/sqs/SQSSubscriber.d.mts +4 -0
- package/dist/sqs/SQSSubscriber.mjs +3 -0
- package/dist/sqs/index.cjs +8 -0
- package/dist/sqs/index.d.cts +5 -0
- package/dist/sqs/index.d.mts +5 -0
- package/dist/sqs/index.mjs +6 -0
- package/dist/types-B63qHVOG.mjs +17 -0
- package/dist/types-B63qHVOG.mjs.map +1 -0
- package/dist/types-CvSMEO5z.cjs +23 -0
- package/dist/types-CvSMEO5z.cjs.map +1 -0
- package/dist/types-D0g-o6wX.d.mts +65 -0
- package/dist/types-DEGUg5BA.d.cts +65 -0
- package/dist/types.cjs +3 -0
- package/dist/types.d.cts +2 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +3 -0
- package/package.json +49 -0
- package/src/EventConnection.ts +39 -0
- package/src/Publisher.ts +87 -0
- package/src/Subscriber.ts +102 -0
- package/src/__tests__/Subscriber.spec.ts +41 -0
- package/src/basic/BasicConnection.ts +47 -0
- package/src/basic/BasicPublisher.ts +15 -0
- package/src/basic/BasicSubscriber.ts +24 -0
- package/src/basic/__tests__/BasicSubscriber.spec.ts +166 -0
- package/src/basic/index.ts +3 -0
- package/src/index.ts +25 -0
- package/src/rabbitmq/RabbitMQConnection.ts +148 -0
- package/src/rabbitmq/RabbitMQPublisher.ts +67 -0
- package/src/rabbitmq/RabbitMQSubscriber.ts +113 -0
- package/src/rabbitmq/__tests__/RabbitMQPublisher.spec.ts +378 -0
- package/src/rabbitmq/__tests__/RabbitMQSubscriber.spec.ts +292 -0
- package/src/rabbitmq/index.ts +6 -0
- package/src/sns/SNSConnection.ts +98 -0
- package/src/sns/SNSPublisher.ts +64 -0
- package/src/sns/SNSSubscriber.ts +222 -0
- package/src/sns/__tests__/SNS-SQS-Integration.spec.ts +389 -0
- package/src/sns/index.ts +6 -0
- package/src/sqs/SQSConnection.ts +97 -0
- package/src/sqs/SQSPublisher.ts +105 -0
- package/src/sqs/SQSSubscriber.ts +189 -0
- package/src/sqs/__tests__/SQSPublisher.spec.ts +189 -0
- package/src/sqs/index.ts +6 -0
- package/src/types.ts +76 -0
- 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)}®ion=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)}®ion=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
|
+
});
|
package/src/sqs/index.ts
ADDED
|
@@ -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
|
+
};
|