@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
@@ -0,0 +1,150 @@
1
+ import * as amqplib from "amqp-connection-manager";
2
+ import * as amqplibCore from "amqplib";
3
+ import { broadcast } from "./events/broadcast.js";
4
+ import { emit } from "./events/emit.js";
5
+ import { emitAndReturn } from "./events/emitAndReturn.js";
6
+ import { emitStreamAndReceiveStream } from "./events/emitStreamAndReceiveStream.js";
7
+ import { randomUUID } from "crypto";
8
+ import { hostname } from "os";
9
+ import { BSBEvents, createConfigSchema, PluginObservable, ResourceContextBuilder, createFakeDTrace, } from "@bsb/base";
10
+ import * as av from "anyvali";
11
+ const ConfigSchema = av.object({
12
+ platformKey: av.nullable(av.string()).default(null).describe("Optional platform key appended to RabbitMQ queue names"),
13
+ fatalOnDisconnect: av.bool().default(true).describe("Whether the process should fail when RabbitMQ disconnects"),
14
+ prefetch: av.int32().default(10).describe("RabbitMQ channel prefetch count"),
15
+ endpoints: av.array(av.string()).default(["amqp://localhost"]).describe("RabbitMQ connection endpoint URLs"),
16
+ credentials: av.object({
17
+ username: av.string().default("guest").describe("RabbitMQ username"),
18
+ password: av.string().default("guest").describe("RabbitMQ password"),
19
+ }).default({ username: "guest", password: "guest" }).describe("RabbitMQ authentication credentials"),
20
+ uniqueId: av.nullable(av.string()).default(null).describe("Optional stable instance ID used in event consumer identity"),
21
+ }).describe("RabbitMQ events plugin configuration");
22
+ export const Config = createConfigSchema({
23
+ name: 'events-rabbitmq',
24
+ description: 'RabbitMQ events plugin for distributed event bus',
25
+ image: './assets/events-rabbitmq.png',
26
+ tags: ['rabbitmq', 'amqp', 'event-bus', 'distributed'],
27
+ documentation: ['./docs/plugin.md'],
28
+ }, ConfigSchema);
29
+ export class Plugin extends BSBEvents {
30
+ constructor(config) {
31
+ super(config);
32
+ this.broadcast = new broadcast(this);
33
+ this.emit = new emit(this);
34
+ this.ear = new emitAndReturn(this);
35
+ this.eas = new emitStreamAndReceiveStream(this);
36
+ }
37
+ getPlatformName(name) {
38
+ if (this.config.platformKey === null)
39
+ return name;
40
+ return `${name}-${this.config.platformKey}`;
41
+ }
42
+ async init(obs) {
43
+ await this._connectToAMQP(obs);
44
+ }
45
+ createObservableFromTrace(trace, attributes) {
46
+ const safeTrace = trace ?? createFakeDTrace("events-rabbitmq", "missing-trace");
47
+ const resource = ResourceContextBuilder.build({
48
+ appId: this.appId,
49
+ mode: this.mode,
50
+ pluginName: this.pluginName,
51
+ cwd: this.cwd,
52
+ packageCwd: this.packageCwd,
53
+ pluginCwd: this.pluginCwd,
54
+ pluginVersion: this.pluginVersion || 'unknown',
55
+ }, this.region);
56
+ return new PluginObservable(safeTrace, resource, this.__internalObservable, attributes || {});
57
+ }
58
+ async _connectToAMQP(obs) {
59
+ const endpoints = this.config.endpoints ?? ["amqp://localhost"];
60
+ const fatalOnDisconnect = this.config.fatalOnDisconnect ?? true;
61
+ const credentials = this.config.credentials;
62
+ obs.log.info('Connect to {endpoints}', {
63
+ endpoints,
64
+ });
65
+ const socketOptions = {
66
+ connectionOptions: {},
67
+ };
68
+ if (credentials?.username) {
69
+ socketOptions.connectionOptions.credentials =
70
+ amqplibCore.credentials.plain(credentials.username, credentials.password ?? "guest");
71
+ }
72
+ this.publishConnection = amqplib.connect(endpoints, socketOptions);
73
+ this.receiveConnection = amqplib.connect(endpoints, socketOptions);
74
+ this.publishConnection.on("connect", async (data) => {
75
+ obs.log.info("AMQP CONNECTED: {url}", { url: data.url });
76
+ });
77
+ this.publishConnection.on("connectFailed", async (data) => {
78
+ const errMsg = data.err?.toString() ?? "unknown";
79
+ if (fatalOnDisconnect || endpoints.length === 1) {
80
+ obs.log.error("AMQP CONNECT FAIL: {url} ({err})", { url: data.url, err: errMsg });
81
+ throw new Error(`AMQP connection failed: ${data.url} (${errMsg})`);
82
+ }
83
+ obs.log.error("AMQP CONNECT FAIL: {url} ({err})", { url: data.url, err: errMsg });
84
+ });
85
+ this.publishConnection.on("error", async (err) => {
86
+ if (err.message !== "Connection closing") {
87
+ obs.log.error("AMQP publish error: {err}", { err: err.message });
88
+ }
89
+ if (fatalOnDisconnect) {
90
+ throw new Error(`AMQP publish error (fatal): ${err.message}`);
91
+ }
92
+ });
93
+ this.receiveConnection.on("error", async (err) => {
94
+ if (err.message !== "Connection closing") {
95
+ obs.log.error("AMQP receive error: {err}", { err: err.message });
96
+ }
97
+ if (fatalOnDisconnect) {
98
+ throw new Error(`AMQP receive error (fatal): ${err.message}`);
99
+ }
100
+ });
101
+ this.publishConnection.on("close", async () => {
102
+ obs.log.warn("AMQP publish connection closed");
103
+ });
104
+ this.receiveConnection.on("close", async () => {
105
+ obs.log.warn("AMQP receive connection closed");
106
+ });
107
+ obs.log.info('Connected to {endpoints}x2? (s:{sendS}/p:{pubS})', {
108
+ endpoints,
109
+ sendS: this.receiveConnection.isConnected(),
110
+ pubS: this.publishConnection.isConnected(),
111
+ });
112
+ this.myId = `${this.config.uniqueId ?? hostname()}-${randomUUID()}`;
113
+ await this.broadcast.init(obs);
114
+ await this.emit.init(obs);
115
+ await this.ear.init(obs);
116
+ }
117
+ dispose() {
118
+ this.broadcast.dispose();
119
+ this.emit.dispose();
120
+ this.ear.dispose();
121
+ this.eas.dispose();
122
+ this.publishConnection.close();
123
+ this.receiveConnection.close();
124
+ }
125
+ async onBroadcast(obs, pluginName, event, listener) {
126
+ await this.broadcast.onBroadcast(obs, pluginName, event, listener);
127
+ }
128
+ async emitBroadcast(obs, pluginName, event, args) {
129
+ await this.broadcast.emitBroadcast(obs, pluginName, event, args);
130
+ }
131
+ async onEvent(obs, pluginName, event, listener) {
132
+ await this.emit.onEvent(obs, pluginName, event, listener);
133
+ }
134
+ async emitEvent(obs, pluginName, event, args) {
135
+ await this.emit.emitEvent(obs, pluginName, event, args);
136
+ }
137
+ async onReturnableEvent(obs, pluginName, event, listener) {
138
+ await this.ear.onReturnableEvent(obs, pluginName, event, listener);
139
+ }
140
+ async emitEventAndReturn(obs, pluginName, event, timeoutSeconds, args) {
141
+ return await this.ear.emitEventAndReturn(obs, pluginName, event, timeoutSeconds, args);
142
+ }
143
+ async receiveStream(obs, pluginName, event, listener, timeoutSeconds) {
144
+ return this.eas.receiveStream(obs, pluginName, event, listener, timeoutSeconds);
145
+ }
146
+ async sendStream(obs, pluginName, event, streamId, stream) {
147
+ return this.eas.sendStream(obs, pluginName, event, streamId, stream);
148
+ }
149
+ }
150
+ Plugin.Config = Config;
@@ -0,0 +1,39 @@
1
+ {
2
+ "key": "events-rabbitmq",
3
+ "name": "RabbitMQ",
4
+ "icon": "events-rabbitmq.png",
5
+ "description": "RabbitMQ events plugin",
6
+ "badges": [
7
+ {
8
+ "url": "https://codecov.io/gh/BetterCorp/service-base-events-rabbitmq",
9
+ "img": "https://codecov.io/gh/BetterCorp/service-base-events-rabbitmq/branch/master/graph/badge.svg"
10
+ },
11
+ {
12
+ "img": "https://img.shields.io/github/license/BetterCorp/service-base-events-rabbitmq"
13
+ },
14
+ {
15
+ "img": "https://img.shields.io/snyk/vulnerabilities/github/BetterCorp/service-base-events-rabbitmq"
16
+ },
17
+ {
18
+ "img": "https://img.shields.io/github/issues-raw/BetterCorp/service-base-events-rabbitmq"
19
+ },
20
+ {
21
+ "img": "https://github.com/BetterCorp/service-base-events-rabbitmq/actions/workflows/develop.yml/badge.svg?branch=develop"
22
+ },
23
+ {
24
+ "img": "https://github.com/BetterCorp/service-base-events-rabbitmq/actions/workflows/master.yml/badge.svg?branch=master"
25
+ },
26
+ {
27
+ "img": "https://img.shields.io/github/issues-raw/BetterCorp/service-base-events-rabbitmq"
28
+ },
29
+ {
30
+ "img": "https://img.shields.io/npm/dt/@bettercorp/service-base-events-rabbitmq"
31
+ },
32
+ {
33
+ "img": "https://img.shields.io/bundlephobia/min/@bettercorp/service-base-events-rabbitmq"
34
+ },
35
+ {
36
+ "img": "https://img.shields.io/npm/v/@bettercorp/service-base-events-rabbitmq"
37
+ }
38
+ ]
39
+ }
@@ -0,0 +1,118 @@
1
+ {
2
+ "pluginName": "events-rabbitmq",
3
+ "events": {},
4
+ "version": "0.0.1",
5
+ "configSchema": {
6
+ "anyvaliVersion": "1.0",
7
+ "schemaVersion": "1.1",
8
+ "root": {
9
+ "kind": "object",
10
+ "properties": {
11
+ "platformKey": {
12
+ "kind": "nullable",
13
+ "inner": {
14
+ "kind": "string"
15
+ },
16
+ "default": null,
17
+ "metadata": {
18
+ "description": "Optional platform key appended to RabbitMQ queue names"
19
+ }
20
+ },
21
+ "fatalOnDisconnect": {
22
+ "kind": "bool",
23
+ "default": true,
24
+ "metadata": {
25
+ "description": "Whether the process should fail when RabbitMQ disconnects"
26
+ }
27
+ },
28
+ "prefetch": {
29
+ "kind": "int32",
30
+ "default": 10,
31
+ "metadata": {
32
+ "description": "RabbitMQ channel prefetch count"
33
+ }
34
+ },
35
+ "endpoints": {
36
+ "kind": "array",
37
+ "items": {
38
+ "kind": "string"
39
+ },
40
+ "default": [
41
+ "amqp://localhost"
42
+ ],
43
+ "metadata": {
44
+ "description": "RabbitMQ connection endpoint URLs"
45
+ }
46
+ },
47
+ "credentials": {
48
+ "kind": "object",
49
+ "properties": {
50
+ "username": {
51
+ "kind": "string",
52
+ "default": "guest",
53
+ "metadata": {
54
+ "description": "RabbitMQ username"
55
+ }
56
+ },
57
+ "password": {
58
+ "kind": "string",
59
+ "default": "guest",
60
+ "metadata": {
61
+ "description": "RabbitMQ password"
62
+ }
63
+ }
64
+ },
65
+ "required": [
66
+ "username",
67
+ "password"
68
+ ],
69
+ "unknownKeys": "strip",
70
+ "default": {
71
+ "username": "guest",
72
+ "password": "guest"
73
+ },
74
+ "metadata": {
75
+ "description": "RabbitMQ authentication credentials"
76
+ }
77
+ },
78
+ "uniqueId": {
79
+ "kind": "nullable",
80
+ "inner": {
81
+ "kind": "string"
82
+ },
83
+ "default": null,
84
+ "metadata": {
85
+ "description": "Optional stable instance ID used in event consumer identity"
86
+ }
87
+ }
88
+ },
89
+ "required": [
90
+ "platformKey",
91
+ "fatalOnDisconnect",
92
+ "prefetch",
93
+ "endpoints",
94
+ "credentials",
95
+ "uniqueId"
96
+ ],
97
+ "unknownKeys": "strip",
98
+ "metadata": {
99
+ "description": "RabbitMQ events plugin configuration"
100
+ }
101
+ },
102
+ "definitions": {},
103
+ "extensions": {}
104
+ },
105
+ "pluginType": "events",
106
+ "capabilities": {
107
+ "eventsApi": {
108
+ "onBroadcast": true,
109
+ "emitBroadcast": true,
110
+ "onEvent": true,
111
+ "emitEvent": true,
112
+ "onReturnableEvent": true,
113
+ "emitEventAndReturn": true,
114
+ "receiveStream": true,
115
+ "sendStream": true
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,124 @@
1
+ {
2
+ "id": "events-rabbitmq",
3
+ "name": "events-rabbitmq",
4
+ "version": "0.0.1",
5
+ "description": "RabbitMQ events plugin for distributed event bus",
6
+ "category": "events",
7
+ "tags": [
8
+ "rabbitmq",
9
+ "amqp",
10
+ "event-bus",
11
+ "distributed"
12
+ ],
13
+ "documentation": [
14
+ "./docs/plugin.md"
15
+ ],
16
+ "dependencies": [],
17
+ "author": {
18
+ "name": "BetterCorp (PTY) Ltd",
19
+ "email": "ninja@bettercorp.dev",
20
+ "url": "https://bettercorp.dev/"
21
+ },
22
+ "license": "(AGPL-3.0-only OR Commercial)",
23
+ "image": "./assets/events-rabbitmq.png",
24
+ "configSchema": {
25
+ "anyvaliVersion": "1.0",
26
+ "schemaVersion": "1.1",
27
+ "root": {
28
+ "kind": "object",
29
+ "properties": {
30
+ "platformKey": {
31
+ "kind": "nullable",
32
+ "inner": {
33
+ "kind": "string"
34
+ },
35
+ "default": null,
36
+ "metadata": {
37
+ "description": "Optional platform key appended to RabbitMQ queue names"
38
+ }
39
+ },
40
+ "fatalOnDisconnect": {
41
+ "kind": "bool",
42
+ "default": true,
43
+ "metadata": {
44
+ "description": "Whether the process should fail when RabbitMQ disconnects"
45
+ }
46
+ },
47
+ "prefetch": {
48
+ "kind": "int32",
49
+ "default": 10,
50
+ "metadata": {
51
+ "description": "RabbitMQ channel prefetch count"
52
+ }
53
+ },
54
+ "endpoints": {
55
+ "kind": "array",
56
+ "items": {
57
+ "kind": "string"
58
+ },
59
+ "default": [
60
+ "amqp://localhost"
61
+ ],
62
+ "metadata": {
63
+ "description": "RabbitMQ connection endpoint URLs"
64
+ }
65
+ },
66
+ "credentials": {
67
+ "kind": "object",
68
+ "properties": {
69
+ "username": {
70
+ "kind": "string",
71
+ "default": "guest",
72
+ "metadata": {
73
+ "description": "RabbitMQ username"
74
+ }
75
+ },
76
+ "password": {
77
+ "kind": "string",
78
+ "default": "guest",
79
+ "metadata": {
80
+ "description": "RabbitMQ password"
81
+ }
82
+ }
83
+ },
84
+ "required": [
85
+ "username",
86
+ "password"
87
+ ],
88
+ "unknownKeys": "strip",
89
+ "default": {
90
+ "username": "guest",
91
+ "password": "guest"
92
+ },
93
+ "metadata": {
94
+ "description": "RabbitMQ authentication credentials"
95
+ }
96
+ },
97
+ "uniqueId": {
98
+ "kind": "nullable",
99
+ "inner": {
100
+ "kind": "string"
101
+ },
102
+ "default": null,
103
+ "metadata": {
104
+ "description": "Optional stable instance ID used in event consumer identity"
105
+ }
106
+ }
107
+ },
108
+ "required": [
109
+ "platformKey",
110
+ "fatalOnDisconnect",
111
+ "prefetch",
112
+ "endpoints",
113
+ "credentials",
114
+ "uniqueId"
115
+ ],
116
+ "unknownKeys": "strip",
117
+ "metadata": {
118
+ "description": "RabbitMQ events plugin configuration"
119
+ }
120
+ },
121
+ "definitions": {},
122
+ "extensions": {}
123
+ }
124
+ }
@@ -0,0 +1,3 @@
1
+ import { BSBEventsConstructor, SBObservable } from "@bsb/base";
2
+ export declare const MockSBObservable: () => SBObservable;
3
+ export declare const getEventsConstructorConfig: (config: any) => BSBEventsConstructor;
@@ -0,0 +1,65 @@
1
+ import { EventEmitter } from "events";
2
+ export const MockSBObservable = () => {
3
+ const observableBus = new EventEmitter();
4
+ observableBus.on("error", () => { });
5
+ return {
6
+ observableBus,
7
+ isReady: true,
8
+ debug: (plugin, trace, message, meta) => {
9
+ observableBus.emit("debug", plugin, trace, message, meta);
10
+ },
11
+ info: (plugin, trace, message, meta) => {
12
+ observableBus.emit("info", plugin, trace, message, meta);
13
+ },
14
+ warn: (plugin, trace, message, meta) => {
15
+ observableBus.emit("warn", plugin, trace, message, meta);
16
+ },
17
+ error: (plugin, trace, message, meta) => {
18
+ observableBus.emit("error", plugin, trace, message, meta);
19
+ },
20
+ createCounter: (timestamp, pluginName, name, description, help, labels) => {
21
+ observableBus.emit("createCounter", timestamp, pluginName, name, description, help, labels);
22
+ },
23
+ incrementCounter: (timestamp, pluginName, name, value, labels) => {
24
+ observableBus.emit("incrementCounter", timestamp, pluginName, name, value, labels);
25
+ },
26
+ createGauge: (timestamp, pluginName, name, description, help, labels) => {
27
+ observableBus.emit("createGauge", timestamp, pluginName, name, description, help, labels);
28
+ },
29
+ setGauge: (timestamp, pluginName, name, value, labels) => {
30
+ observableBus.emit("setGauge", timestamp, pluginName, name, value, labels);
31
+ },
32
+ createHistogram: (timestamp, pluginName, name, description, help, boundaries, labels) => {
33
+ observableBus.emit("createHistogram", timestamp, pluginName, name, description, help, boundaries, labels);
34
+ },
35
+ observeHistogram: (timestamp, pluginName, name, value, labels) => {
36
+ observableBus.emit("observeHistogram", timestamp, pluginName, name, value, labels);
37
+ },
38
+ startSpan: (timestamp, appId, pluginName, traceId, parentSpanId, spanId, name, attributes) => {
39
+ observableBus.emit("spanStart", { t: traceId, s: spanId }, pluginName, name, attributes);
40
+ },
41
+ endSpan: (timestamp, appId, pluginName, traceId, spanId, attributes) => {
42
+ observableBus.emit("spanEnd", { t: traceId, s: spanId }, pluginName, attributes);
43
+ },
44
+ errorSpan: (timestamp, appId, pluginName, traceId, spanId, error, attributes) => {
45
+ observableBus.emit("spanError", { t: traceId, s: spanId }, pluginName, error, attributes);
46
+ },
47
+ setupObservablePlugins: async () => { },
48
+ init: async () => { },
49
+ run: async () => { },
50
+ dispose: async () => { },
51
+ };
52
+ };
53
+ export const getEventsConstructorConfig = (config) => {
54
+ return {
55
+ appId: "test-app",
56
+ packageCwd: process.cwd(),
57
+ pluginCwd: process.cwd(),
58
+ cwd: process.cwd(),
59
+ mode: "development",
60
+ pluginName: "test-plugin",
61
+ pluginVersion: "0.0.0",
62
+ sbObservable: MockSBObservable(),
63
+ config: config,
64
+ };
65
+ };
@@ -0,0 +1,4 @@
1
+ import { BSBEvents } from "@bsb/base";
2
+ export declare function broadcast(genNewPlugin: {
3
+ (): Promise<BSBEvents>;
4
+ }, maxTimeoutToExpectAResponse: number): void;