@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.
- package/LICENSE +661 -0
- package/README.md +109 -0
- package/lib/plugins/events-rabbitmq/events/broadcast.d.ts +19 -0
- package/lib/plugins/events-rabbitmq/events/broadcast.js +105 -0
- package/lib/plugins/events-rabbitmq/events/emit.d.ts +17 -0
- package/lib/plugins/events-rabbitmq/events/emit.js +89 -0
- package/lib/plugins/events-rabbitmq/events/emitAndReturn.d.ts +20 -0
- package/lib/plugins/events-rabbitmq/events/emitAndReturn.js +207 -0
- package/lib/plugins/events-rabbitmq/events/emitStreamAndReceiveStream.d.ts +24 -0
- package/lib/plugins/events-rabbitmq/events/emitStreamAndReceiveStream.js +515 -0
- package/lib/plugins/events-rabbitmq/events/lib.d.ts +12 -0
- package/lib/plugins/events-rabbitmq/events/lib.js +59 -0
- package/lib/plugins/events-rabbitmq/index.d.ts +55 -0
- package/lib/plugins/events-rabbitmq/index.js +150 -0
- package/lib/plugins/events-rabbitmq/plugin.config.json +39 -0
- package/lib/schemas/events-rabbitmq.json +118 -0
- package/lib/schemas/events-rabbitmq.plugin.json +124 -0
- package/lib/tests/mocks.d.ts +3 -0
- package/lib/tests/mocks.js +65 -0
- package/lib/tests/plugins/events/events/broadcast.d.ts +4 -0
- package/lib/tests/plugins/events/events/broadcast.js +320 -0
- package/lib/tests/plugins/events/events/emit.d.ts +4 -0
- package/lib/tests/plugins/events/events/emit.js +316 -0
- package/lib/tests/plugins/events/events/emitAndReturn.d.ts +4 -0
- package/lib/tests/plugins/events/events/emitAndReturn.js +307 -0
- package/lib/tests/plugins/events/events/emitStreamAndReceiveStream.d.ts +4 -0
- package/lib/tests/plugins/events/events/emitStreamAndReceiveStream.js +199 -0
- package/lib/tests/plugins/events/plugin.d.ts +2 -0
- package/lib/tests/plugins/events/plugin.js +47 -0
- package/lib/tests/trace.d.ts +4 -0
- package/lib/tests/trace.js +39 -0
- 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
|
+
}
|