@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
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ // Generic types and interfaces
2
+ export type {
3
+ EventConnection,
4
+ EventPublisher,
5
+ EventSubscriber,
6
+ ExtractPublisherMessage,
7
+ MappedEvent,
8
+ PublishableMessage,
9
+ } from './types';
10
+ export { EventPublisherType } from './types';
11
+
12
+ // Generic factories
13
+ export { Publisher } from './Publisher';
14
+ export type { EventPublisherConnectionString } from './Publisher';
15
+
16
+ export { Subscriber } from './Subscriber';
17
+ export type { EventSubscriberConnectionString } from './Subscriber';
18
+
19
+ export { EventConnectionFactory } from './EventConnection';
20
+
21
+ // Specific integrations should be imported via subpaths:
22
+ // - @geekmidas/events/basic
23
+ // - @geekmidas/events/rabbitmq
24
+ // - @geekmidas/events/sqs
25
+ // - @geekmidas/events/sns
@@ -0,0 +1,148 @@
1
+ import amqplib from 'amqplib';
2
+ import type { EventConnection } from '../types';
3
+ import { EventPublisherType } from '../types';
4
+
5
+ export interface RabbitMQConnectionConfig {
6
+ url: string;
7
+ exchange?: string;
8
+ exchangeType?: 'topic' | 'direct' | 'fanout' | 'headers';
9
+ exchangeOptions?: amqplib.Options.AssertExchange;
10
+ timeout?: number; // Connection timeout in milliseconds (default: 5000)
11
+ }
12
+
13
+ export class RabbitMQConnection implements EventConnection {
14
+ readonly type = EventPublisherType.RabbitMQ;
15
+ private connection?: amqplib.ChannelModel;
16
+ private channel?: amqplib.Channel;
17
+ private connecting?: Promise<void>;
18
+ private config: Required<
19
+ Pick<RabbitMQConnectionConfig, 'exchange' | 'exchangeType' | 'timeout'>
20
+ > &
21
+ RabbitMQConnectionConfig;
22
+
23
+ constructor(config: RabbitMQConnectionConfig) {
24
+ this.config = {
25
+ ...config,
26
+ exchange: config.exchange || 'geekmidas.events',
27
+ exchangeType: config.exchangeType || 'topic',
28
+ timeout: config.timeout ?? 5000,
29
+ exchangeOptions: {
30
+ durable: true,
31
+ ...config.exchangeOptions,
32
+ },
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Create a RabbitMQConnection from a connection string
38
+ * Format: rabbitmq://user:pass@host:port/vhost?exchange=name&type=topic&timeout=5000
39
+ */
40
+ static async fromConnectionString(
41
+ connectionString: string,
42
+ ): Promise<RabbitMQConnection> {
43
+ const url = new URL(connectionString);
44
+ const params = url.searchParams;
45
+
46
+ // Extract connection URL without query params
47
+ const baseUrl = `amqp://${url.username ? `${url.username}:${url.password}@` : ''}${url.host}${url.pathname}`;
48
+
49
+ const timeoutParam = params.get('timeout');
50
+ const config: RabbitMQConnectionConfig = {
51
+ url: baseUrl,
52
+ exchange: params.get('exchange') || 'geekmidas.events',
53
+ exchangeType: (params.get('type') as any) || 'topic',
54
+ timeout: timeoutParam ? Number.parseInt(timeoutParam, 10) : undefined,
55
+ };
56
+
57
+ const connection = new RabbitMQConnection(config);
58
+ await connection.connect();
59
+ return connection;
60
+ }
61
+
62
+ async connect(): Promise<void> {
63
+ if (this.channel) return;
64
+
65
+ // Prevent multiple simultaneous connection attempts
66
+ if (this.connecting) {
67
+ await this.connecting;
68
+ return;
69
+ }
70
+
71
+ this.connecting = (async () => {
72
+ try {
73
+ const connection = await amqplib.connect(this.config.url, {
74
+ timeout: this.config.timeout,
75
+ });
76
+ const channel = await connection.createChannel();
77
+
78
+ // Assert the exchange exists
79
+ await channel.assertExchange(
80
+ this.config.exchange!,
81
+ this.config.exchangeType!,
82
+ this.config.exchangeOptions,
83
+ );
84
+
85
+ // Handle connection errors
86
+ connection.on('error', (_err: Error) => {
87
+ this.cleanup();
88
+ });
89
+
90
+ connection.on('close', () => {
91
+ this.cleanup();
92
+ });
93
+
94
+ // Only set after successful setup
95
+ this.connection = connection;
96
+ this.channel = channel;
97
+ } catch (error) {
98
+ this.cleanup();
99
+ throw error;
100
+ }
101
+ })();
102
+
103
+ await this.connecting;
104
+ this.connecting = undefined;
105
+ }
106
+
107
+ async close(): Promise<void> {
108
+ try {
109
+ await this.channel?.close();
110
+ await this.connection?.close();
111
+ } catch (error) {
112
+ // Ignore errors during close
113
+ } finally {
114
+ this.cleanup();
115
+ }
116
+ }
117
+
118
+ isConnected(): boolean {
119
+ return !!this.channel;
120
+ }
121
+
122
+ private cleanup(): void {
123
+ this.channel = undefined;
124
+ this.connection = undefined;
125
+ this.connecting = undefined;
126
+ }
127
+
128
+ /**
129
+ * Get the underlying channel for publishing/subscribing
130
+ */
131
+ get amqpChannel(): amqplib.Channel | undefined {
132
+ return this.channel;
133
+ }
134
+
135
+ /**
136
+ * Get the exchange name for this connection
137
+ */
138
+ get exchangeName(): string {
139
+ return this.config.exchange;
140
+ }
141
+
142
+ /**
143
+ * Get the exchange type for this connection
144
+ */
145
+ get exchangeType(): string {
146
+ return this.config.exchangeType;
147
+ }
148
+ }
@@ -0,0 +1,67 @@
1
+ import type amqplib from 'amqplib';
2
+ import type { EventPublisher, PublishableMessage } from '../types';
3
+ import type { RabbitMQConnection } from './RabbitMQConnection';
4
+
5
+ export interface RabbitMQPublisherOptions {
6
+ publishOptions?: amqplib.Options.Publish;
7
+ }
8
+
9
+ export class RabbitMQPublisher<TMessage extends PublishableMessage<string, any>>
10
+ implements EventPublisher<TMessage>
11
+ {
12
+ constructor(
13
+ private connection: RabbitMQConnection,
14
+ private options: RabbitMQPublisherOptions = {},
15
+ ) {}
16
+
17
+ /**
18
+ * Create a RabbitMQPublisher from a connection string
19
+ * Format: rabbitmq://user:pass@host:port/vhost?exchange=name&type=topic&timeout=5000
20
+ */
21
+ static async fromConnectionString<
22
+ TMessage extends PublishableMessage<string, any>,
23
+ >(connectionString: string): Promise<RabbitMQPublisher<TMessage>> {
24
+ const { RabbitMQConnection } = await import('./RabbitMQConnection');
25
+ const connection =
26
+ await RabbitMQConnection.fromConnectionString(connectionString);
27
+ return new RabbitMQPublisher<TMessage>(connection);
28
+ }
29
+
30
+ async publish(messages: TMessage[]): Promise<void> {
31
+ if (!this.connection.isConnected()) {
32
+ await this.connection.connect();
33
+ }
34
+
35
+ const channel = this.connection.amqpChannel;
36
+ if (!channel) {
37
+ throw new Error('Channel not initialized');
38
+ }
39
+
40
+ const exchange = this.connection.exchangeName;
41
+
42
+ for (const message of messages) {
43
+ const content = Buffer.from(JSON.stringify(message.payload));
44
+
45
+ // Use message type as routing key for topic exchanges
46
+ const routingKey = message.type;
47
+
48
+ const published = channel.publish(exchange, routingKey, content, {
49
+ contentType: 'application/json',
50
+ timestamp: Date.now(),
51
+ type: message.type,
52
+ persistent: true,
53
+ ...this.options.publishOptions,
54
+ });
55
+
56
+ // Handle backpressure
57
+ if (!published) {
58
+ await new Promise((resolve) => channel.once('drain', resolve));
59
+ }
60
+ }
61
+ }
62
+
63
+ async close(): Promise<void> {
64
+ // Publisher doesn't own the connection
65
+ // Connection should be closed by whoever created it
66
+ }
67
+ }
@@ -0,0 +1,113 @@
1
+ import type amqplib from 'amqplib';
2
+ import type { EventSubscriber, PublishableMessage } from '../types';
3
+ import type { RabbitMQConnection } from './RabbitMQConnection';
4
+
5
+ export interface RabbitMQSubscriberOptions {
6
+ queueName?: string; // If not provided, a unique queue will be generated
7
+ queueOptions?: amqplib.Options.AssertQueue;
8
+ consumeOptions?: amqplib.Options.Consume;
9
+ prefetch?: number; // Number of messages to prefetch (default: 1)
10
+ }
11
+
12
+ export class RabbitMQSubscriber<
13
+ TMessage extends PublishableMessage<string, any>,
14
+ > implements EventSubscriber<TMessage>
15
+ {
16
+ constructor(
17
+ private connection: RabbitMQConnection,
18
+ private options: RabbitMQSubscriberOptions = {},
19
+ ) {}
20
+
21
+ /**
22
+ * Create a RabbitMQSubscriber from a connection string
23
+ * Format: rabbitmq://user:pass@host:port/vhost?exchange=name&type=topic&queueName=myqueue&prefetch=10
24
+ */
25
+ static async fromConnectionString<
26
+ TMessage extends PublishableMessage<string, any>,
27
+ >(connectionString: string): Promise<RabbitMQSubscriber<TMessage>> {
28
+ const url = new URL(connectionString);
29
+ const params = url.searchParams;
30
+
31
+ const { RabbitMQConnection } = await import('./RabbitMQConnection');
32
+ const connection =
33
+ await RabbitMQConnection.fromConnectionString(connectionString);
34
+
35
+ const options: RabbitMQSubscriberOptions = {
36
+ queueName: params.get('queueName') || undefined,
37
+ prefetch: params.get('prefetch')
38
+ ? Number.parseInt(params.get('prefetch')!, 10)
39
+ : undefined,
40
+ };
41
+
42
+ return new RabbitMQSubscriber<TMessage>(connection, options);
43
+ }
44
+
45
+ async subscribe(
46
+ messages: TMessage['type'][],
47
+ listener: (payload: TMessage) => Promise<void>,
48
+ ): Promise<void> {
49
+ if (!this.connection.isConnected()) {
50
+ await this.connection.connect();
51
+ }
52
+
53
+ const channel = this.connection.amqpChannel;
54
+ if (!channel) {
55
+ throw new Error('Channel not initialized');
56
+ }
57
+
58
+ const exchange = this.connection.exchangeName;
59
+
60
+ // Set prefetch if specified
61
+ if (this.options.prefetch) {
62
+ await channel.prefetch(this.options.prefetch);
63
+ }
64
+
65
+ // Assert queue
66
+ const queueName = this.options.queueName || '';
67
+ const queueOptions: amqplib.Options.AssertQueue = {
68
+ durable: true,
69
+ ...this.options.queueOptions,
70
+ };
71
+
72
+ const { queue } = await channel.assertQueue(queueName, queueOptions);
73
+
74
+ // Bind queue to exchange for each message type
75
+ for (const messageType of messages) {
76
+ await channel.bindQueue(queue, exchange, messageType);
77
+ }
78
+
79
+ // Consume messages
80
+ await channel.consume(
81
+ queue,
82
+ async (msg) => {
83
+ if (!msg) return;
84
+
85
+ try {
86
+ // Parse message payload
87
+ const content = msg.content.toString();
88
+ const payload = JSON.parse(content);
89
+
90
+ // Get message type from properties
91
+ const messageType = msg.properties.type;
92
+
93
+ // Reconstruct full message
94
+ const fullMessage: TMessage = {
95
+ type: messageType,
96
+ payload,
97
+ } as TMessage;
98
+
99
+ // Call listener
100
+ await listener(fullMessage);
101
+
102
+ // Ack message
103
+ channel.ack(msg);
104
+ } catch (error) {
105
+ // Log error and nack message (requeue)
106
+ console.error('Error processing message:', error);
107
+ channel.nack(msg, false, true);
108
+ }
109
+ },
110
+ this.options.consumeOptions,
111
+ );
112
+ }
113
+ }