@bsb/events-rabbitmq 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 (32) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +109 -0
  3. package/lib/plugins/events-rabbitmq/events/broadcast.d.ts +19 -0
  4. package/lib/plugins/events-rabbitmq/events/broadcast.js +105 -0
  5. package/lib/plugins/events-rabbitmq/events/emit.d.ts +17 -0
  6. package/lib/plugins/events-rabbitmq/events/emit.js +89 -0
  7. package/lib/plugins/events-rabbitmq/events/emitAndReturn.d.ts +20 -0
  8. package/lib/plugins/events-rabbitmq/events/emitAndReturn.js +207 -0
  9. package/lib/plugins/events-rabbitmq/events/emitStreamAndReceiveStream.d.ts +24 -0
  10. package/lib/plugins/events-rabbitmq/events/emitStreamAndReceiveStream.js +515 -0
  11. package/lib/plugins/events-rabbitmq/events/lib.d.ts +12 -0
  12. package/lib/plugins/events-rabbitmq/events/lib.js +59 -0
  13. package/lib/plugins/events-rabbitmq/index.d.ts +55 -0
  14. package/lib/plugins/events-rabbitmq/index.js +150 -0
  15. package/lib/plugins/events-rabbitmq/plugin.config.json +39 -0
  16. package/lib/schemas/events-rabbitmq.json +118 -0
  17. package/lib/schemas/events-rabbitmq.plugin.json +124 -0
  18. package/lib/tests/mocks.d.ts +3 -0
  19. package/lib/tests/mocks.js +65 -0
  20. package/lib/tests/plugins/events/events/broadcast.d.ts +4 -0
  21. package/lib/tests/plugins/events/events/broadcast.js +320 -0
  22. package/lib/tests/plugins/events/events/emit.d.ts +4 -0
  23. package/lib/tests/plugins/events/events/emit.js +316 -0
  24. package/lib/tests/plugins/events/events/emitAndReturn.d.ts +4 -0
  25. package/lib/tests/plugins/events/events/emitAndReturn.js +307 -0
  26. package/lib/tests/plugins/events/events/emitStreamAndReceiveStream.d.ts +4 -0
  27. package/lib/tests/plugins/events/events/emitStreamAndReceiveStream.js +199 -0
  28. package/lib/tests/plugins/events/plugin.d.ts +2 -0
  29. package/lib/tests/plugins/events/plugin.js +47 -0
  30. package/lib/tests/trace.d.ts +4 -0
  31. package/lib/tests/trace.js +39 -0
  32. package/package.json +70 -0
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # @bsb/events-rabbitmq
2
+
3
+ RabbitMQ events plugin for BSB that provides a distributed event bus using AMQP. It enables communication between multiple processes, containers, and microservices with reliable delivery and advanced routing.
4
+
5
+ ## Key Features
6
+
7
+ - Distributed event bus across multiple processes and containers
8
+ - Full support for all BSB event patterns (fire-and-forget, request-response, broadcast, streaming)
9
+ - RabbitMQ cluster support with automatic reconnection
10
+ - Reliable message delivery with acknowledgments
11
+ - Configurable prefetch for load balancing
12
+ - Platform isolation with multi-tenancy support
13
+ - Unique client identification for routing
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @bsb/events-rabbitmq
19
+ ```
20
+
21
+ ## Configuration
22
+
23
+ Add the plugin to your BSB configuration file:
24
+
25
+ ```yaml
26
+ plugins:
27
+ events:
28
+ plugin: "@bsb/events-rabbitmq"
29
+ enabled: true
30
+ config:
31
+ endpoints:
32
+ - "amqp://localhost:5672"
33
+ credentials:
34
+ username: "guest"
35
+ password: "guest"
36
+ prefetch: 10
37
+ fatalOnDisconnect: true
38
+ platformKey: null
39
+ uniqueId: null
40
+ ```
41
+
42
+ ### Configuration Options
43
+
44
+ | Option | Description | Default |
45
+ |--------|-------------|---------|
46
+ | `endpoints` | Array of RabbitMQ server URLs (cluster support) | `["amqp://localhost"]` |
47
+ | `credentials.username` | RabbitMQ username | `guest` |
48
+ | `credentials.password` | RabbitMQ password | `guest` |
49
+ | `prefetch` | Messages to prefetch per consumer | `10` |
50
+ | `fatalOnDisconnect` | Exit process on connection loss | `true` |
51
+ | `platformKey` | Isolate multiple BSB platforms on same RabbitMQ | `null` |
52
+ | `uniqueId` | Static client ID (uses hostname if not set) | `null` |
53
+
54
+ ## Usage
55
+
56
+ Once configured, the plugin provides the same API as `events-default`, but distributed across RabbitMQ.
57
+
58
+ ### Fire-and-Forget
59
+
60
+ ```typescript
61
+ await this.events.emitEvent("order.created", {
62
+ orderId: "12345",
63
+ items: [{ sku: "ABC", qty: 2 }]
64
+ });
65
+ ```
66
+
67
+ ### Request-Response
68
+
69
+ ```typescript
70
+ const result = await this.events.emitEventAndReturn(
71
+ "user.validate",
72
+ { email: "user@example.com" },
73
+ 5000
74
+ );
75
+ ```
76
+
77
+ ### Broadcast
78
+
79
+ ```typescript
80
+ await this.events.emitBroadcast("cache.invalidate", { keys: ["a", "b"] });
81
+ ```
82
+
83
+ ### Streaming
84
+
85
+ ```typescript
86
+ const streamId = await this.events.receiveStream("file.upload", handler, 30);
87
+ await this.events.sendStream("file.upload", streamId, fileStream);
88
+ ```
89
+
90
+ ## RabbitMQ Setup
91
+
92
+ For local development, the RabbitMQ management image is a quick start:
93
+
94
+ ```bash
95
+ docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management
96
+ ```
97
+
98
+ ## Documentation
99
+
100
+ Detailed documentation (used by the BSB Registry): `https://github.com/BetterCorp/better-service-base/blob/master/plugins/nodejs/events-rabbitmq/docs/plugin.md`
101
+
102
+ ## Links
103
+
104
+ - GitHub: `https://github.com/BetterCorp/better-service-base/tree/master/plugins/nodejs/events-rabbitmq`
105
+ - BSB Registry (package): `https://io.bsbcode.dev/packages/nodejs/@bsb/events-rabbitmq`
106
+
107
+ ## License
108
+
109
+ (AGPL-3.0-only OR Commercial)
@@ -0,0 +1,19 @@
1
+ import { Plugin } from "../index.js";
2
+ import { Observable } from "@bsb/base";
3
+ export declare class broadcast {
4
+ private plugin;
5
+ private publishQueuesSetup;
6
+ private publishChannel;
7
+ private receiveChannel;
8
+ private readonly channelKey;
9
+ private readonly exchange;
10
+ private readonly exchangeOpts;
11
+ private readonly queueOpts;
12
+ constructor(plugin: Plugin);
13
+ init(obs: Observable): Promise<void>;
14
+ dispose(): void;
15
+ onBroadcast(obs: Observable, pluginName: string, event: string, listener: {
16
+ (obs: Observable, args: Array<any>): Promise<void>;
17
+ }): Promise<void>;
18
+ emitBroadcast(obs: Observable, pluginName: string, event: string, args: Array<any>): Promise<void>;
19
+ }
@@ -0,0 +1,105 @@
1
+ import { LIB } from "./lib.js";
2
+ import { randomUUID } from "crypto";
3
+ import { SmartFunctionCallAsync, } from "@bsb/base";
4
+ export class broadcast {
5
+ constructor(plugin) {
6
+ this.publishQueuesSetup = [];
7
+ this.channelKey = "91eb";
8
+ this.exchange = {
9
+ type: "fanout",
10
+ name: "better.service9.broadcast",
11
+ };
12
+ this.exchangeOpts = {
13
+ durable: false,
14
+ autoDelete: false,
15
+ };
16
+ this.queueOpts = {
17
+ durable: false,
18
+ autoDelete: false,
19
+ messageTtl: 60 * 60 * 1000,
20
+ expires: 60 * 60 * 1000,
21
+ };
22
+ this.plugin = plugin;
23
+ }
24
+ async init(obs) {
25
+ obs.log.debug("Open broadcast channel ({exchangeName})", {
26
+ exchangeName: this.exchange.name,
27
+ });
28
+ this.publishChannel = await LIB.setupChannel(this.plugin, obs, this.plugin.publishConnection, this.channelKey, this.exchange.name, this.exchange.type, this.exchangeOpts);
29
+ this.receiveChannel = await LIB.setupChannel(this.plugin, obs, this.plugin.receiveConnection, this.channelKey, this.exchange.name, this.exchange.type, this.exchangeOpts, 5);
30
+ }
31
+ dispose() {
32
+ this.publishChannel.channel.close();
33
+ this.receiveChannel.channel.close();
34
+ }
35
+ async onBroadcast(obs, pluginName, event, listener) {
36
+ const thisUUID = randomUUID();
37
+ const rawQueueKey = LIB.getQueueKey(this.plugin, this.channelKey, pluginName, event);
38
+ const thisQueueKey = LIB.getQueueKey(this.plugin, this.channelKey, pluginName, event, thisUUID);
39
+ obs.log.debug("LISTEN: [{thisQueueKey}]", {
40
+ thisQueueKey: rawQueueKey,
41
+ });
42
+ await this.receiveChannel.channel.addSetup(async (iChannel) => {
43
+ await iChannel.assertQueue(thisQueueKey, this.queueOpts);
44
+ await this.receiveChannel.channel.consume(thisQueueKey, async (msg) => {
45
+ const body = msg.content.toString();
46
+ const bodyObj = JSON.parse(body);
47
+ let listenerObs = null;
48
+ try {
49
+ const rootObs = this.plugin.createObservableFromTrace(bodyObj.trace, {
50
+ pluginName,
51
+ event,
52
+ });
53
+ listenerObs = rootObs.startSpan("broadcast.listener", {
54
+ pluginName,
55
+ event,
56
+ });
57
+ await SmartFunctionCallAsync(this.plugin, listener, listenerObs, bodyObj.args ?? []);
58
+ this.receiveChannel.channel.ack(msg);
59
+ }
60
+ catch (err) {
61
+ const errorObj = err instanceof Error ? err : new Error(err?.message || String(err));
62
+ if (listenerObs) {
63
+ listenerObs.error(errorObj);
64
+ }
65
+ this.receiveChannel.channel.nack(msg, true);
66
+ obs.log.error("broadcast listener error: {err}", { err: errorObj.message });
67
+ }
68
+ finally {
69
+ if (listenerObs) {
70
+ listenerObs.end();
71
+ }
72
+ }
73
+ }, { noAck: false });
74
+ await iChannel.bindQueue(thisQueueKey, this.receiveChannel.exchangeName, rawQueueKey);
75
+ obs.log.debug("listen rabbit: [{thisQueueKey}]", {
76
+ thisQueueKey: rawQueueKey,
77
+ });
78
+ });
79
+ }
80
+ async emitBroadcast(obs, pluginName, event, args) {
81
+ const thisQueueKey = LIB.getQueueKey(this.plugin, this.channelKey, pluginName, event);
82
+ obs.log.debug("Emit: [{thisQueueKey}]", {
83
+ thisQueueKey,
84
+ });
85
+ if (!this.publishQueuesSetup.includes(thisQueueKey)) {
86
+ this.publishQueuesSetup.push(thisQueueKey);
87
+ await this.publishChannel.channel.addSetup(async (iChannel) => {
88
+ await iChannel.assertQueue(thisQueueKey, this.queueOpts);
89
+ obs.log.debug("emit rabbit: [{thisQueueKey}]", { thisQueueKey });
90
+ });
91
+ }
92
+ if (!await this.publishChannel.channel.publish(this.publishChannel.exchangeName, thisQueueKey, {
93
+ trace: obs.trace,
94
+ args,
95
+ }, {
96
+ expiration: this.queueOpts.messageTtl,
97
+ contentType: "string",
98
+ appId: this.plugin.myId,
99
+ timestamp: Date.now(),
100
+ })) {
101
+ throw `Cannot send msg to queue [${thisQueueKey}]`;
102
+ }
103
+ obs.log.debug(" - EMIT: [{thisQueueKey}] - EMITTED", { thisQueueKey });
104
+ }
105
+ }
@@ -0,0 +1,17 @@
1
+ import { Plugin } from "../index.js";
2
+ import { Observable } from "@bsb/base";
3
+ export declare class emit {
4
+ private plugin;
5
+ private publishQueuesSetup;
6
+ private publishChannel;
7
+ private receiveChannel;
8
+ private readonly channelKey;
9
+ private readonly queueOpts;
10
+ constructor(plugin: Plugin);
11
+ init(obs: Observable): Promise<void>;
12
+ dispose(): void;
13
+ onEvent(obs: Observable, pluginName: string, event: string, listener: {
14
+ (obs: Observable, args: Array<any>): Promise<void>;
15
+ }): Promise<void>;
16
+ emitEvent(obs: Observable, pluginName: string, event: string, args: Array<any>): Promise<void>;
17
+ }
@@ -0,0 +1,89 @@
1
+ import { LIB } from "./lib.js";
2
+ import { SmartFunctionCallAsync, } from "@bsb/base";
3
+ export class emit {
4
+ constructor(plugin) {
5
+ this.publishQueuesSetup = [];
6
+ this.channelKey = "91eq";
7
+ this.queueOpts = {
8
+ durable: false,
9
+ autoDelete: false,
10
+ messageTtl: 60 * 60 * 1000,
11
+ expires: 60 * 60 * 1000,
12
+ };
13
+ this.plugin = plugin;
14
+ }
15
+ async init(obs) {
16
+ obs.log.debug("Open broadcast channel ({channelKey})", {
17
+ channelKey: this.channelKey,
18
+ });
19
+ this.publishChannel = await LIB.setupChannel(this.plugin, obs, this.plugin.publishConnection, this.channelKey, null);
20
+ this.receiveChannel = await LIB.setupChannel(this.plugin, obs, this.plugin.receiveConnection, this.channelKey, null, undefined, undefined, 5);
21
+ }
22
+ dispose() {
23
+ this.publishChannel.channel.close();
24
+ this.receiveChannel.channel.close();
25
+ }
26
+ async onEvent(obs, pluginName, event, listener) {
27
+ const thisQueueKey = LIB.getQueueKey(this.plugin, this.channelKey, pluginName, event);
28
+ obs.log.debug("LISTEN: [{thisQueueKey}]", { thisQueueKey });
29
+ await this.receiveChannel.channel.addSetup(async (iChannel) => {
30
+ await iChannel.assertQueue(thisQueueKey, this.queueOpts);
31
+ await this.receiveChannel.channel.consume(thisQueueKey, async (msg) => {
32
+ const body = msg.content.toString();
33
+ const bodyObj = JSON.parse(body);
34
+ let listenerObs = null;
35
+ try {
36
+ const rootObs = this.plugin.createObservableFromTrace(bodyObj.trace, {
37
+ pluginName,
38
+ event,
39
+ });
40
+ listenerObs = rootObs.startSpan("event.listener", {
41
+ pluginName,
42
+ event,
43
+ });
44
+ await SmartFunctionCallAsync(this.plugin, listener, listenerObs, bodyObj.args ?? []);
45
+ this.receiveChannel.channel.ack(msg);
46
+ }
47
+ catch (err) {
48
+ const errorObj = err instanceof Error ? err : new Error(err?.message || String(err));
49
+ if (listenerObs) {
50
+ listenerObs.error(errorObj);
51
+ }
52
+ this.receiveChannel.channel.nack(msg, true);
53
+ obs.log.error("event listener error: {err}", { err: errorObj.message });
54
+ }
55
+ finally {
56
+ if (listenerObs) {
57
+ listenerObs.end();
58
+ }
59
+ }
60
+ }, { noAck: false });
61
+ obs.log.debug("listen rabbit: [{thisQueueKey}]", { thisQueueKey });
62
+ });
63
+ }
64
+ async emitEvent(obs, pluginName, event, args) {
65
+ const thisQueueKey = LIB.getQueueKey(this.plugin, this.channelKey, pluginName, event);
66
+ obs.log.debug("Emit: [{thisQueueKey}]", {
67
+ thisQueueKey,
68
+ });
69
+ if (!this.publishQueuesSetup.includes(thisQueueKey)) {
70
+ this.publishQueuesSetup.push(thisQueueKey);
71
+ await this.publishChannel.channel.addSetup(async (iChannel) => {
72
+ await iChannel.assertQueue(thisQueueKey, this.queueOpts);
73
+ obs.log.debug("emit rabbit: [{thisQueueKey}]", { thisQueueKey });
74
+ });
75
+ }
76
+ if (!await this.publishChannel.channel.sendToQueue(thisQueueKey, {
77
+ trace: obs.trace,
78
+ args,
79
+ }, {
80
+ expiration: this.queueOpts.messageTtl,
81
+ contentType: "string",
82
+ appId: this.plugin.myId,
83
+ timestamp: Date.now(),
84
+ })) {
85
+ throw new Error(`Cannot send msg to queue [${thisQueueKey}]`);
86
+ }
87
+ obs.log.debug(" - EMIT: [{thisQueueKey}] - EMITTED", { thisQueueKey });
88
+ }
89
+ }
@@ -0,0 +1,20 @@
1
+ import { Plugin } from "../index.js";
2
+ import { EventEmitter } from "events";
3
+ import { Observable } from "@bsb/base";
4
+ export declare class emitAndReturn extends EventEmitter {
5
+ private plugin;
6
+ private privateQueuesSetup;
7
+ private publishChannel;
8
+ private receiveChannel;
9
+ private readonly channelKey;
10
+ private readonly myChannelKey;
11
+ private readonly queueOpts;
12
+ private readonly myQueueOpts;
13
+ constructor(plugin: Plugin);
14
+ init(obs: Observable): Promise<void>;
15
+ dispose(): void;
16
+ onReturnableEvent(obs: Observable, pluginName: string, event: string, listener: {
17
+ (obs: Observable, args: Array<any>): Promise<any>;
18
+ }): Promise<void>;
19
+ emitEventAndReturn(obs: Observable, pluginName: string, event: string, timeoutSeconds: number, args: Array<any>): Promise<any>;
20
+ }
@@ -0,0 +1,207 @@
1
+ import { EventEmitter } from "events";
2
+ import { randomUUID } from "crypto";
3
+ import { LIB } from "./lib.js";
4
+ import { BSBError, SmartFunctionCallAsync, } from "@bsb/base";
5
+ export class emitAndReturn extends EventEmitter {
6
+ constructor(plugin) {
7
+ super();
8
+ this.privateQueuesSetup = [];
9
+ this.channelKey = "91ar";
10
+ this.myChannelKey = "91kr";
11
+ this.queueOpts = {
12
+ durable: false,
13
+ autoDelete: false,
14
+ messageTtl: 60 * 1000,
15
+ expires: 60 * 1000,
16
+ };
17
+ this.myQueueOpts = {
18
+ exclusive: true,
19
+ durable: false,
20
+ autoDelete: false,
21
+ messageTtl: 60 * 1000,
22
+ expires: 60 * 1000,
23
+ };
24
+ this.plugin = plugin;
25
+ }
26
+ async init(obs) {
27
+ const myEARQueueKey = LIB.getMyQueueKey(this.plugin, this.myChannelKey, this.plugin.myId);
28
+ obs.log.debug("Ready my events name: {myEARQueueKey}", {
29
+ myEARQueueKey,
30
+ });
31
+ this.publishChannel = await LIB.setupChannel(this.plugin, obs, this.plugin.publishConnection, this.myChannelKey, null);
32
+ this.receiveChannel = await LIB.setupChannel(this.plugin, obs, this.plugin.receiveConnection, this.myChannelKey, null, undefined, undefined, 2);
33
+ await this.receiveChannel.channel.addSetup(async (iChannel) => {
34
+ await iChannel.assertQueue(myEARQueueKey, this.myQueueOpts);
35
+ obs.log.debug("LISTEN: [{myEARQueueKey}]", { myEARQueueKey });
36
+ await iChannel.consume(myEARQueueKey, (msg) => {
37
+ if (msg === null) {
38
+ obs.log.warn("[RECEIVED {myEARQueueKey}]... as null", {
39
+ myEARQueueKey,
40
+ });
41
+ return;
42
+ }
43
+ try {
44
+ const body = msg.content.toString();
45
+ obs.log.debug("[RECEIVED {myEARQueueKey}]", {
46
+ myEARQueueKey,
47
+ });
48
+ this.emit(msg.properties.correlationId, JSON.parse(body));
49
+ iChannel.ack(msg);
50
+ }
51
+ catch (exc) {
52
+ obs.log.error("AMQP Consumed exception: {eMsg}", {
53
+ eMsg: exc.message || exc.toString(),
54
+ });
55
+ throw new Error(`AMQP consume exception: ${exc.message || exc}`);
56
+ }
57
+ }, { noAck: false });
58
+ obs.log.debug("LISTEN: [{myEARQueueKey}]", { myEARQueueKey });
59
+ obs.log.debug("Ready my events name: {myEARQueueKey} OKAY", {
60
+ myEARQueueKey,
61
+ });
62
+ });
63
+ }
64
+ dispose() {
65
+ this.publishChannel.channel.close();
66
+ this.receiveChannel.channel.close();
67
+ }
68
+ async onReturnableEvent(obs, pluginName, event, listener) {
69
+ const queueKey = LIB.getQueueKey(this.plugin, this.channelKey, pluginName, event);
70
+ obs.log.debug("EAR: listen {queueKey}", {
71
+ queueKey,
72
+ });
73
+ await this.receiveChannel.channel.addSetup(async (iChannel) => {
74
+ await iChannel.assertQueue(queueKey, this.queueOpts);
75
+ await iChannel.consume(queueKey, async (msg) => {
76
+ if (msg === null) {
77
+ return obs.log.error("Message received on my EAR queue was null...");
78
+ }
79
+ const returnQueue = LIB.getMyQueueKey(this.plugin, this.myChannelKey, msg.properties.appId);
80
+ obs.log.debug("EAR: Received: {queueKey} from {returnQueue}", {
81
+ queueKey,
82
+ returnQueue,
83
+ });
84
+ const body = msg.content.toString();
85
+ const bodyObj = JSON.parse(body);
86
+ let handlerObs = null;
87
+ try {
88
+ const rootObs = this.plugin.createObservableFromTrace(bodyObj.trace, {
89
+ pluginName,
90
+ event,
91
+ });
92
+ handlerObs = rootObs.startSpan("emitAndReturn.handler", {
93
+ pluginName,
94
+ event,
95
+ });
96
+ const response = await SmartFunctionCallAsync(this.plugin, listener, handlerObs, bodyObj.args ?? []);
97
+ iChannel.ack(msg);
98
+ obs.log.debug("EAR: OKAY: {queueKey} -> {returnQueue}", {
99
+ queueKey,
100
+ returnQueue,
101
+ });
102
+ if (!await this.publishChannel.channel.sendToQueue(returnQueue, {
103
+ trace: handlerObs.trace,
104
+ result: response,
105
+ }, {
106
+ expiration: 5000,
107
+ correlationId: `${msg.properties.correlationId}-resolve`,
108
+ contentType: "string",
109
+ appId: this.plugin.myId,
110
+ timestamp: Date.now(),
111
+ })) {
112
+ throw new BSBError(handlerObs.trace, "Cannot send msg to queue [{returnQueue}]", { returnQueue });
113
+ }
114
+ }
115
+ catch (exc) {
116
+ const errorObj = exc instanceof Error ? exc : new Error(String(exc));
117
+ if (handlerObs) {
118
+ handlerObs.error(errorObj);
119
+ }
120
+ obs.log.error("EAR: ERROR: {queueKey} -> {returnQueue}", {
121
+ queueKey,
122
+ returnQueue,
123
+ });
124
+ if (!await this.publishChannel.channel.sendToQueue(returnQueue, {
125
+ trace: bodyObj.trace,
126
+ error: errorObj.message,
127
+ }, {
128
+ expiration: 5000,
129
+ correlationId: `${msg.properties.correlationId}-reject`,
130
+ contentType: "string",
131
+ appId: this.plugin.myId,
132
+ timestamp: Date.now(),
133
+ })) {
134
+ throw new BSBError(obs.trace, "Cannot send msg to queue [{returnQueue}]", { returnQueue });
135
+ }
136
+ iChannel.ack(msg);
137
+ }
138
+ finally {
139
+ if (handlerObs) {
140
+ handlerObs.end();
141
+ }
142
+ }
143
+ }, { noAck: false });
144
+ obs.log.debug("EAR: listening {queueKey}", {
145
+ queueKey,
146
+ });
147
+ });
148
+ }
149
+ async emitEventAndReturn(obs, pluginName, event, timeoutSeconds, args) {
150
+ const start = Date.now();
151
+ const resultKey = `${randomUUID()}-${start}${Math.random()}`;
152
+ const queueKey = LIB.getQueueKey(this.plugin, this.channelKey, pluginName, event);
153
+ const requestObs = obs.startSpan("emitAndReturn.request", {
154
+ pluginName,
155
+ event,
156
+ correlationId: resultKey,
157
+ });
158
+ requestObs.log.debug("EAR: emitting {queueKey} ({resultKey})", {
159
+ queueKey,
160
+ resultKey,
161
+ });
162
+ if (!this.privateQueuesSetup.includes(queueKey)) {
163
+ this.privateQueuesSetup.push(queueKey);
164
+ await this.publishChannel.channel.addSetup(async (iChannel) => {
165
+ await iChannel.assertQueue(queueKey, this.queueOpts);
166
+ });
167
+ }
168
+ return new Promise(async (resolve, reject) => {
169
+ const timeoutHandler = setTimeout(() => {
170
+ this.removeAllListeners(`${resultKey}-resolve`);
171
+ this.removeAllListeners(`${resultKey}-reject`);
172
+ const err = new Error("Timeout");
173
+ requestObs.error(err);
174
+ requestObs.end();
175
+ reject(err);
176
+ }, timeoutSeconds * 1000);
177
+ this.once(`${resultKey}-resolve`, async (rargs) => {
178
+ clearTimeout(timeoutHandler);
179
+ requestObs.end();
180
+ resolve(rargs?.result ?? rargs);
181
+ });
182
+ this.once(`${resultKey}-reject`, async (rargs) => {
183
+ clearTimeout(timeoutHandler);
184
+ const err = new Error(rargs?.error || "Unknown error");
185
+ requestObs.error(err);
186
+ requestObs.end();
187
+ reject(err);
188
+ });
189
+ if (!await this.publishChannel.channel.sendToQueue(queueKey, {
190
+ trace: requestObs.trace,
191
+ args,
192
+ }, {
193
+ expiration: timeoutSeconds * 1000 + 5000,
194
+ correlationId: resultKey,
195
+ contentType: "string",
196
+ appId: this.plugin.myId,
197
+ timestamp: Date.now(),
198
+ })) {
199
+ throw new BSBError(requestObs.trace, "Cannot send msg to queue [{queueKey}]", { queueKey });
200
+ }
201
+ requestObs.log.debug("EAR: emitted {queueKey} ({resultKey})", {
202
+ queueKey,
203
+ resultKey,
204
+ });
205
+ });
206
+ }
207
+ }
@@ -0,0 +1,24 @@
1
+ import { EventEmitter } from "events";
2
+ import { Readable } from "stream";
3
+ import { Plugin } from "../index.js";
4
+ import { Observable } from "@bsb/base";
5
+ export declare class emitStreamAndReceiveStream extends EventEmitter {
6
+ private readonly staticCommsTimeout;
7
+ private plugin;
8
+ private eventsChannel;
9
+ private streamChannel;
10
+ private readonly eventsChannelKey;
11
+ private readonly streamChannelKey;
12
+ private readonly queueOpts;
13
+ private myEventsQueueKey;
14
+ private myStreamQueueKey;
15
+ private cleanupSelf;
16
+ constructor(plugin: Plugin);
17
+ dispose(): void;
18
+ setupChannel(obs: Observable, channel: any, channelKey: string, queueKeyMethod: Function, logMessage: string): Promise<void>;
19
+ setupChannelsIfNotSetup(obs: Observable): Promise<void>;
20
+ receiveStream(obs: Observable, pluginName: string, event: string, listener: {
21
+ (obs: Observable, error: Error | null, stream: Readable): Promise<void>;
22
+ }, timeoutSeconds?: number): Promise<string>;
23
+ sendStream(obs: Observable, pluginName: string, event: string, streamIdf: string, stream: Readable): Promise<void>;
24
+ }