@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,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
package/dist/types.d.cts
ADDED
|
@@ -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 };
|
package/dist/types.d.mts
ADDED
|
@@ -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
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';
|
package/src/Publisher.ts
ADDED
|
@@ -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®ion=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®ion=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®ion=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
|
+
});
|