@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,65 @@
1
+ import { InferStandardSchema } from "@geekmidas/schema";
2
+ import { StandardSchemaV1 } from "@standard-schema/spec";
3
+
4
+ //#region src/types.d.ts
5
+
6
+ /**
7
+ * Represents a publishable event message with a type and payload.
8
+ *
9
+ * @template TType - The event type/name (e.g., 'user.created')
10
+ * @template TPayload - The event payload data
11
+ */
12
+ type PublishableMessage<TType extends string, TPayload> = {
13
+ type: TType;
14
+ payload: TPayload;
15
+ };
16
+ /**
17
+ * Event publisher/subscriber types
18
+ */
19
+ declare enum EventPublisherType {
20
+ Basic = "basic",
21
+ EventBridge = "eventbridge",
22
+ SQS = "sqs",
23
+ SNS = "sns",
24
+ Kafka = "kafka",
25
+ RabbitMQ = "rabbitmq",
26
+ }
27
+ /**
28
+ * Base interface for event connections
29
+ * Connections manage the underlying transport (RabbitMQ channel, SQS client, etc.)
30
+ */
31
+ interface EventConnection {
32
+ readonly type: EventPublisherType;
33
+ connect(): Promise<void>;
34
+ close(): Promise<void>;
35
+ isConnected(): boolean;
36
+ }
37
+ /**
38
+ * Interface for event publishers that handle the actual publishing of events.
39
+ * Implementations can send to EventBridge, SQS, Kafka, or any other event system.
40
+ *
41
+ * @template TMessage - The union type of all publishable messages
42
+ */
43
+ type EventPublisher<TMessage extends PublishableMessage<string, any>> = {
44
+ publish: (message: TMessage[]) => Promise<void>;
45
+ };
46
+ /**
47
+ * Interface for event subscribers that handle receiving and processing events.
48
+ *
49
+ * @template TMessage - The union type of all publishable messages
50
+ */
51
+ type EventSubscriber<TMessage extends PublishableMessage<string, any>> = {
52
+ subscribe: (messages: TMessage['type'][], listener: (payload: TMessage) => Promise<void>) => Promise<void>;
53
+ };
54
+ /**
55
+ * Utility type to extract the message from EventPublisher
56
+ */
57
+ type ExtractPublisherMessage<T> = T extends EventPublisher<infer M> ? M : never;
58
+ type MappedEvent<T extends EventPublisher<any> | undefined, OutSchema extends StandardSchemaV1 | undefined = undefined> = {
59
+ type: ExtractPublisherMessage<T>['type'];
60
+ payload: (ctx: InferStandardSchema<OutSchema>) => ExtractPublisherMessage<T>['payload'];
61
+ when?: (ctx: InferStandardSchema<OutSchema>) => boolean;
62
+ };
63
+ //#endregion
64
+ export { EventConnection, EventPublisher, EventPublisherType, EventSubscriber, ExtractPublisherMessage, MappedEvent, PublishableMessage };
65
+ //# sourceMappingURL=types-DEGUg5BA.d.cts.map
package/dist/types.cjs ADDED
@@ -0,0 +1,3 @@
1
+ const require_types = require('./types-CvSMEO5z.cjs');
2
+
3
+ exports.EventPublisherType = require_types.EventPublisherType;
@@ -0,0 +1,2 @@
1
+ import { EventConnection, EventPublisher, EventPublisherType, EventSubscriber, ExtractPublisherMessage, MappedEvent, PublishableMessage } from "./types-DEGUg5BA.cjs";
2
+ export { EventConnection, EventPublisher, EventPublisherType, EventSubscriber, ExtractPublisherMessage, MappedEvent, PublishableMessage };
@@ -0,0 +1,2 @@
1
+ import { EventConnection, EventPublisher, EventPublisherType, EventSubscriber, ExtractPublisherMessage, MappedEvent, PublishableMessage } from "./types-D0g-o6wX.mjs";
2
+ export { EventConnection, EventPublisher, EventPublisherType, EventSubscriber, ExtractPublisherMessage, MappedEvent, PublishableMessage };
package/dist/types.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { EventPublisherType } from "./types-B63qHVOG.mjs";
2
+
3
+ export { EventPublisherType };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@geekmidas/events",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.mjs",
9
+ "require": "./dist/index.cjs"
10
+ },
11
+ "./basic": {
12
+ "types": "./dist/basic/index.d.ts",
13
+ "import": "./dist/basic/index.mjs",
14
+ "require": "./dist/basic/index.cjs"
15
+ },
16
+ "./rabbitmq": {
17
+ "types": "./dist/rabbitmq/index.d.ts",
18
+ "import": "./dist/rabbitmq/index.mjs",
19
+ "require": "./dist/rabbitmq/index.cjs"
20
+ },
21
+ "./sqs": {
22
+ "types": "./dist/sqs/index.d.ts",
23
+ "import": "./dist/sqs/index.mjs",
24
+ "require": "./dist/sqs/index.cjs"
25
+ },
26
+ "./sns": {
27
+ "types": "./dist/sns/index.d.ts",
28
+ "import": "./dist/sns/index.mjs",
29
+ "require": "./dist/sns/index.cjs"
30
+ }
31
+ },
32
+ "publishConfig": {
33
+ "registry": "https://registry.npmjs.org/",
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "lodash.chunk": "~4.2.0",
38
+ "@geekmidas/schema": "0.0.1"
39
+ },
40
+ "peerDependencies": {
41
+ "amqplib": "^0.10.3",
42
+ "@aws-sdk/client-sqs": "~3.908.0",
43
+ "@aws-sdk/client-sns": "~3.908.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/amqplib": "~0.10.7",
47
+ "@types/lodash.chunk": "~4.2.0"
48
+ }
49
+ }
@@ -0,0 +1,39 @@
1
+ import type { EventConnection } from './types';
2
+ import { EventPublisherType } from './types';
3
+
4
+ export class EventConnectionFactory {
5
+ /**
6
+ * Create an EventConnection from a connection string
7
+ * Dynamically imports the appropriate connection implementation
8
+ */
9
+ static async fromConnectionString(
10
+ connectionStr: string,
11
+ ): Promise<EventConnection> {
12
+ const url = new URL(connectionStr);
13
+ const protocol = url.protocol.replace(':', '') as EventPublisherType;
14
+
15
+ switch (protocol) {
16
+ case EventPublisherType.Basic: {
17
+ const { BasicConnection } = await import('./basic');
18
+ return BasicConnection.fromConnectionString(connectionStr);
19
+ }
20
+ case EventPublisherType.RabbitMQ: {
21
+ const { RabbitMQConnection } = await import('./rabbitmq');
22
+ return RabbitMQConnection.fromConnectionString(connectionStr);
23
+ }
24
+ case EventPublisherType.SQS: {
25
+ const { SQSConnection } = await import('./sqs');
26
+ return SQSConnection.fromConnectionString(connectionStr);
27
+ }
28
+ case EventPublisherType.SNS: {
29
+ const { SNSConnection } = await import('./sns');
30
+ return SNSConnection.fromConnectionString(connectionStr);
31
+ }
32
+ default:
33
+ throw new Error(`Unsupported connection type: ${protocol}`);
34
+ }
35
+ }
36
+ }
37
+
38
+ export { EventPublisherType } from './types';
39
+ export type { EventConnection } from './types';
@@ -0,0 +1,87 @@
1
+ import type {
2
+ EventConnection,
3
+ EventPublisher,
4
+ PublishableMessage,
5
+ } from './types';
6
+ import { EventPublisherType } from './types';
7
+
8
+ export class Publisher {
9
+ /**
10
+ * Create a publisher from a connection string
11
+ * This creates both a connection and a publisher
12
+ */
13
+ static async fromConnectionString<
14
+ TMessage extends PublishableMessage<string, any>,
15
+ >(
16
+ connectionStr: EventPublisherConnectionString,
17
+ ): Promise<EventPublisher<TMessage>> {
18
+ const url = new URL(connectionStr);
19
+ switch (url.protocol.replace(':', '')) {
20
+ case EventPublisherType.Basic: {
21
+ const { BasicConnection, BasicPublisher } = await import('./basic');
22
+ const connection =
23
+ await BasicConnection.fromConnectionString(connectionStr);
24
+ return new BasicPublisher<TMessage>(connection);
25
+ }
26
+ case EventPublisherType.RabbitMQ: {
27
+ const { RabbitMQPublisher } = await import('./rabbitmq');
28
+ return RabbitMQPublisher.fromConnectionString<TMessage>(connectionStr);
29
+ }
30
+ case EventPublisherType.SQS: {
31
+ const { SQSPublisher } = await import('./sqs');
32
+ return SQSPublisher.fromConnectionString<TMessage>(connectionStr);
33
+ }
34
+ case EventPublisherType.SNS: {
35
+ const { SNSPublisher } = await import('./sns');
36
+ return SNSPublisher.fromConnectionString<TMessage>(connectionStr);
37
+ }
38
+ // Future implementations for EventBridge, Kafka, etc.
39
+ default:
40
+ throw new Error(`Unsupported event publisher type: ${url.protocol}`);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Create a publisher from an existing connection
46
+ * This allows sharing connections between publishers and subscribers
47
+ */
48
+ static async fromConnection<TMessage extends PublishableMessage<string, any>>(
49
+ connection: EventConnection,
50
+ ): Promise<EventPublisher<TMessage>> {
51
+ switch (connection.type) {
52
+ case EventPublisherType.Basic: {
53
+ const { BasicPublisher } = await import('./basic');
54
+ const { BasicConnection } = await import('./basic');
55
+ return new BasicPublisher<TMessage>(
56
+ connection as InstanceType<typeof BasicConnection>,
57
+ );
58
+ }
59
+ case EventPublisherType.RabbitMQ: {
60
+ const { RabbitMQPublisher } = await import('./rabbitmq');
61
+ const { RabbitMQConnection } = await import('./rabbitmq');
62
+ return new RabbitMQPublisher<TMessage>(
63
+ connection as InstanceType<typeof RabbitMQConnection>,
64
+ );
65
+ }
66
+ case EventPublisherType.SQS: {
67
+ const { SQSPublisher } = await import('./sqs');
68
+ const { SQSConnection } = await import('./sqs');
69
+ return new SQSPublisher<TMessage>(
70
+ connection as InstanceType<typeof SQSConnection>,
71
+ );
72
+ }
73
+ case EventPublisherType.SNS: {
74
+ const { SNSPublisher } = await import('./sns');
75
+ const { SNSConnection } = await import('./sns');
76
+ return new SNSPublisher<TMessage>(
77
+ connection as InstanceType<typeof SNSConnection>,
78
+ );
79
+ }
80
+ default:
81
+ throw new Error(`Unsupported connection type: ${connection.type}`);
82
+ }
83
+ }
84
+ }
85
+
86
+ export type EventPublisherConnectionString =
87
+ `${EventPublisherType}://${string}`;
@@ -0,0 +1,102 @@
1
+ import type {
2
+ EventConnection,
3
+ EventSubscriber,
4
+ PublishableMessage,
5
+ } from './types';
6
+ import { EventPublisherType } from './types';
7
+
8
+ export class Subscriber {
9
+ /**
10
+ * Create a subscriber from a connection string
11
+ * This creates both a connection and a subscriber
12
+ */
13
+ static async fromConnectionString<
14
+ TMessage extends PublishableMessage<string, any>,
15
+ >(
16
+ connectionStr: EventSubscriberConnectionString,
17
+ ): Promise<EventSubscriber<TMessage>> {
18
+ const url = new URL(connectionStr);
19
+ switch (url.protocol.replace(':', '')) {
20
+ case EventPublisherType.Basic: {
21
+ const { BasicConnection, BasicSubscriber } = await import('./basic');
22
+ const connection =
23
+ await BasicConnection.fromConnectionString(connectionStr);
24
+ return new BasicSubscriber<TMessage>(connection);
25
+ }
26
+ case EventPublisherType.RabbitMQ: {
27
+ const { RabbitMQSubscriber } = await import('./rabbitmq');
28
+ return RabbitMQSubscriber.fromConnectionString<TMessage>(connectionStr);
29
+ }
30
+ case EventPublisherType.SNS: {
31
+ const { SNSSubscriber } = await import('./sns');
32
+ return SNSSubscriber.fromConnectionString<TMessage>(connectionStr);
33
+ }
34
+ case EventPublisherType.SQS: {
35
+ const url = new URL(connectionStr);
36
+ const topicArn = url.searchParams.get('topicArn');
37
+
38
+ // If topicArn is present, use managed SNS→SQS subscription
39
+ if (topicArn) {
40
+ const { SNSSubscriber } = await import('./sns');
41
+ // Convert SQS connection string to SNS connection string
42
+ const snsConnectionStr = connectionStr.replace('sqs://', 'sns://');
43
+ return SNSSubscriber.fromConnectionString<TMessage>(snsConnectionStr);
44
+ }
45
+
46
+ // Direct SQS subscriber
47
+ const { SQSSubscriber } = await import('./sqs');
48
+ const { SQSConnection } = await import('./sqs');
49
+ const connection =
50
+ await SQSConnection.fromConnectionString(connectionStr);
51
+ return new SQSSubscriber<TMessage>(connection);
52
+ }
53
+ // Future implementations for EventBridge, Kafka, etc.
54
+ default:
55
+ throw new Error(`Unsupported event subscriber type: ${url.protocol}`);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Create a subscriber from an existing connection
61
+ * This allows sharing connections between publishers and subscribers
62
+ */
63
+ static async fromConnection<TMessage extends PublishableMessage<string, any>>(
64
+ connection: EventConnection,
65
+ ): Promise<EventSubscriber<TMessage>> {
66
+ switch (connection.type) {
67
+ case EventPublisherType.Basic: {
68
+ const { BasicSubscriber } = await import('./basic');
69
+ const { BasicConnection } = await import('./basic');
70
+ return new BasicSubscriber<TMessage>(
71
+ connection as InstanceType<typeof BasicConnection>,
72
+ );
73
+ }
74
+ case EventPublisherType.RabbitMQ: {
75
+ const { RabbitMQSubscriber } = await import('./rabbitmq');
76
+ const { RabbitMQConnection } = await import('./rabbitmq');
77
+ return new RabbitMQSubscriber<TMessage>(
78
+ connection as InstanceType<typeof RabbitMQConnection>,
79
+ );
80
+ }
81
+ case EventPublisherType.SNS: {
82
+ const { SNSSubscriber } = await import('./sns');
83
+ const { SNSConnection } = await import('./sns');
84
+ return new SNSSubscriber<TMessage>(
85
+ connection as InstanceType<typeof SNSConnection>,
86
+ );
87
+ }
88
+ case EventPublisherType.SQS: {
89
+ const { SQSSubscriber } = await import('./sqs');
90
+ const { SQSConnection } = await import('./sqs');
91
+ return new SQSSubscriber<TMessage>(
92
+ connection as InstanceType<typeof SQSConnection>,
93
+ );
94
+ }
95
+ default:
96
+ throw new Error(`Unsupported connection type: ${connection.type}`);
97
+ }
98
+ }
99
+ }
100
+
101
+ export type EventSubscriberConnectionString =
102
+ `${EventPublisherType}://${string}`;
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Subscriber } from '../Subscriber';
3
+ import { SNSSubscriber } from '../sns/SNSSubscriber';
4
+ import { SQSSubscriber } from '../sqs/SQSSubscriber';
5
+ import type { PublishableMessage } from '../types';
6
+
7
+ type TestMessage = PublishableMessage<'test.event', { data: string }>;
8
+
9
+ describe('Subscriber', () => {
10
+ describe('fromConnectionString', () => {
11
+ it('should create SQS subscriber without topicArn', async () => {
12
+ const connectionStr =
13
+ 'sqs://?queueUrl=https://sqs.us-east-1.amazonaws.com/123456789/test-queue&region=us-east-1&endpoint=http://localhost:4566';
14
+
15
+ const subscriber =
16
+ await Subscriber.fromConnectionString<TestMessage>(connectionStr);
17
+
18
+ expect(subscriber).toBeInstanceOf(SQSSubscriber);
19
+ });
20
+
21
+ it('should create SNS subscriber when topicArn is present in SQS connection string', async () => {
22
+ const connectionStr =
23
+ 'sqs://?topicArn=arn:aws:sns:us-east-1:123456789:test-topic&queueName=test-queue&region=us-east-1&endpoint=http://localhost:4566&accessKeyId=test&secretAccessKey=test';
24
+
25
+ const subscriber =
26
+ await Subscriber.fromConnectionString<TestMessage>(connectionStr);
27
+
28
+ expect(subscriber).toBeInstanceOf(SNSSubscriber);
29
+ });
30
+
31
+ it('should create SNS subscriber from sns:// connection string', async () => {
32
+ const connectionStr =
33
+ 'sns://?topicArn=arn:aws:sns:us-east-1:123456789:test-topic&region=us-east-1&endpoint=http://localhost:4566&accessKeyId=test&secretAccessKey=test';
34
+
35
+ const subscriber =
36
+ await Subscriber.fromConnectionString<TestMessage>(connectionStr);
37
+
38
+ expect(subscriber).toBeInstanceOf(SNSSubscriber);
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,47 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { EventConnection } from '../types';
3
+ import { EventPublisherType } from '../types';
4
+
5
+ export class BasicConnection implements EventConnection {
6
+ readonly type = EventPublisherType.Basic;
7
+ private emitter: EventEmitter;
8
+ private connected = false;
9
+
10
+ constructor() {
11
+ this.emitter = new EventEmitter();
12
+ }
13
+
14
+ /**
15
+ * Create a BasicConnection from a connection string
16
+ * Format: basic://
17
+ */
18
+ static async fromConnectionString(
19
+ _connectionString: string,
20
+ ): Promise<BasicConnection> {
21
+ // Basic connection doesn't need any configuration
22
+ const connection = new BasicConnection();
23
+ await connection.connect();
24
+ return connection;
25
+ }
26
+
27
+ async connect(): Promise<void> {
28
+ this.connected = true;
29
+ }
30
+
31
+ async close(): Promise<void> {
32
+ this.emitter.removeAllListeners();
33
+ this.connected = false;
34
+ }
35
+
36
+ isConnected(): boolean {
37
+ return this.connected;
38
+ }
39
+
40
+ /**
41
+ * Get the underlying EventEmitter
42
+ * Used by BasicPublisher and BasicSubscriber
43
+ */
44
+ get eventEmitter(): EventEmitter {
45
+ return this.emitter;
46
+ }
47
+ }
@@ -0,0 +1,15 @@
1
+ import type { EventPublisher, PublishableMessage } from '../types';
2
+ import type { BasicConnection } from './BasicConnection';
3
+
4
+ export class BasicPublisher<TMessage extends PublishableMessage<string, any>>
5
+ implements EventPublisher<TMessage>
6
+ {
7
+ constructor(private connection: BasicConnection) {}
8
+
9
+ async publish(messages: TMessage[]) {
10
+ const emitter = this.connection.eventEmitter;
11
+ for (const message of messages) {
12
+ emitter.emit(message.type, message);
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,24 @@
1
+ import type { EventSubscriber, PublishableMessage } from '../types';
2
+ import type { BasicConnection } from './BasicConnection';
3
+
4
+ export class BasicSubscriber<TMessage extends PublishableMessage<string, any>>
5
+ implements EventSubscriber<TMessage>
6
+ {
7
+ constructor(private connection: BasicConnection) {}
8
+
9
+ async subscribe(
10
+ messages: TMessage['type'][],
11
+ listener: (payload: TMessage) => Promise<void>,
12
+ ): Promise<void> {
13
+ const emitter = this.connection.eventEmitter;
14
+
15
+ for (const messageType of messages) {
16
+ emitter.on(messageType, (message: TMessage) => {
17
+ listener(message).catch((error) => {
18
+ // Log error but don't throw to prevent unhandled promise rejections
19
+ console.error(`Error processing message ${messageType}:`, error);
20
+ });
21
+ });
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,166 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { PublishableMessage } from '../../types';
3
+ import { BasicConnection } from '../BasicConnection';
4
+ import { BasicPublisher } from '../BasicPublisher';
5
+ import { BasicSubscriber } from '../BasicSubscriber';
6
+
7
+ type TestMessage = PublishableMessage<'user.created' | 'user.updated', any>;
8
+
9
+ describe('BasicSubscriber', () => {
10
+ it('should receive published messages', async () => {
11
+ const connection = new BasicConnection();
12
+ await connection.connect();
13
+
14
+ const publisher = new BasicPublisher<TestMessage>(connection);
15
+ const subscriber = new BasicSubscriber<TestMessage>(connection);
16
+
17
+ const receivedMessages: TestMessage[] = [];
18
+
19
+ await subscriber.subscribe(['user.created'], async (message) => {
20
+ receivedMessages.push(message);
21
+ });
22
+
23
+ await publisher.publish([
24
+ { type: 'user.created', payload: { userId: '123' } },
25
+ ]);
26
+
27
+ // Give event emitter time to process
28
+ await new Promise((resolve) => setTimeout(resolve, 10));
29
+
30
+ expect(receivedMessages).toHaveLength(1);
31
+ expect(receivedMessages[0]).toEqual({
32
+ type: 'user.created',
33
+ payload: { userId: '123' },
34
+ });
35
+
36
+ await connection.close();
37
+ });
38
+
39
+ it('should receive multiple message types', async () => {
40
+ const connection = new BasicConnection();
41
+ await connection.connect();
42
+
43
+ const publisher = new BasicPublisher<TestMessage>(connection);
44
+ const subscriber = new BasicSubscriber<TestMessage>(connection);
45
+
46
+ const receivedMessages: TestMessage[] = [];
47
+
48
+ await subscriber.subscribe(
49
+ ['user.created', 'user.updated'],
50
+ async (message) => {
51
+ receivedMessages.push(message);
52
+ },
53
+ );
54
+
55
+ await publisher.publish([
56
+ { type: 'user.created', payload: { userId: '1' } },
57
+ { type: 'user.updated', payload: { userId: '2' } },
58
+ { type: 'user.created', payload: { userId: '3' } },
59
+ ]);
60
+
61
+ // Give event emitter time to process
62
+ await new Promise((resolve) => setTimeout(resolve, 10));
63
+
64
+ expect(receivedMessages).toHaveLength(3);
65
+ expect(receivedMessages[0].type).toBe('user.created');
66
+ expect(receivedMessages[1].type).toBe('user.updated');
67
+ expect(receivedMessages[2].type).toBe('user.created');
68
+
69
+ await connection.close();
70
+ });
71
+
72
+ it('should only receive subscribed message types', async () => {
73
+ const connection = new BasicConnection();
74
+ await connection.connect();
75
+
76
+ const publisher = new BasicPublisher<TestMessage>(connection);
77
+ const subscriber = new BasicSubscriber<TestMessage>(connection);
78
+
79
+ const receivedMessages: TestMessage[] = [];
80
+
81
+ // Only subscribe to user.created
82
+ await subscriber.subscribe(['user.created'], async (message) => {
83
+ receivedMessages.push(message);
84
+ });
85
+
86
+ await publisher.publish([
87
+ { type: 'user.created', payload: { userId: '1' } },
88
+ { type: 'user.updated', payload: { userId: '2' } },
89
+ { type: 'user.created', payload: { userId: '3' } },
90
+ ]);
91
+
92
+ // Give event emitter time to process
93
+ await new Promise((resolve) => setTimeout(resolve, 10));
94
+
95
+ expect(receivedMessages).toHaveLength(2);
96
+ expect(receivedMessages.every((m) => m.type === 'user.created')).toBe(true);
97
+
98
+ await connection.close();
99
+ });
100
+
101
+ it('should handle multiple subscribers on same connection', async () => {
102
+ const connection = new BasicConnection();
103
+ await connection.connect();
104
+
105
+ const publisher = new BasicPublisher<TestMessage>(connection);
106
+ const subscriber1 = new BasicSubscriber<TestMessage>(connection);
107
+ const subscriber2 = new BasicSubscriber<TestMessage>(connection);
108
+
109
+ const messages1: TestMessage[] = [];
110
+ const messages2: TestMessage[] = [];
111
+
112
+ await subscriber1.subscribe(['user.created'], async (message) => {
113
+ messages1.push(message);
114
+ });
115
+
116
+ await subscriber2.subscribe(['user.created'], async (message) => {
117
+ messages2.push(message);
118
+ });
119
+
120
+ await publisher.publish([
121
+ { type: 'user.created', payload: { userId: '123' } },
122
+ ]);
123
+
124
+ // Give event emitter time to process
125
+ await new Promise((resolve) => setTimeout(resolve, 10));
126
+
127
+ // Both subscribers should receive the message
128
+ expect(messages1).toHaveLength(1);
129
+ expect(messages2).toHaveLength(1);
130
+
131
+ await connection.close();
132
+ });
133
+
134
+ it('should handle async listener errors gracefully', async () => {
135
+ const connection = new BasicConnection();
136
+ await connection.connect();
137
+
138
+ const publisher = new BasicPublisher<TestMessage>(connection);
139
+ const subscriber = new BasicSubscriber<TestMessage>(connection);
140
+
141
+ const receivedMessages: TestMessage[] = [];
142
+
143
+ await subscriber.subscribe(['user.created'], async (message) => {
144
+ if (message.payload.userId === '2') {
145
+ throw new Error('Test error');
146
+ }
147
+ receivedMessages.push(message);
148
+ });
149
+
150
+ await publisher.publish([
151
+ { type: 'user.created', payload: { userId: '1' } },
152
+ { type: 'user.created', payload: { userId: '2' } },
153
+ { type: 'user.created', payload: { userId: '3' } },
154
+ ]);
155
+
156
+ // Give event emitter time to process
157
+ await new Promise((resolve) => setTimeout(resolve, 10));
158
+
159
+ // Should receive messages 1 and 3 (2 throws error)
160
+ expect(receivedMessages).toHaveLength(2);
161
+ expect(receivedMessages[0].payload.userId).toBe('1');
162
+ expect(receivedMessages[1].payload.userId).toBe('3');
163
+
164
+ await connection.close();
165
+ });
166
+ });
@@ -0,0 +1,3 @@
1
+ export { BasicConnection } from './BasicConnection';
2
+ export { BasicPublisher } from './BasicPublisher';
3
+ export { BasicSubscriber } from './BasicSubscriber';