@opra/rabbitmq 1.15.0 → 1.16.0

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.
@@ -6,7 +6,7 @@ const node_zlib_1 = tslib_1.__importDefault(require("node:zlib"));
6
6
  const type_is_1 = tslib_1.__importDefault(require("@browsery/type-is"));
7
7
  const common_1 = require("@opra/common");
8
8
  const core_1 = require("@opra/core");
9
- const amqplib_1 = tslib_1.__importDefault(require("amqplib"));
9
+ const amqp_connection_manager_1 = require("amqp-connection-manager");
10
10
  const content_type_1 = require("content-type");
11
11
  const iconv_lite_1 = tslib_1.__importDefault(require("iconv-lite"));
12
12
  const util_1 = require("util");
@@ -34,7 +34,6 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
34
34
  constructor(document, config) {
35
35
  super(config);
36
36
  this._controllerInstances = new Map();
37
- this._connections = [];
38
37
  this._status = 'idle';
39
38
  this.protocol = 'rpc';
40
39
  this.platform = RabbitmqAdapter.PlatformName;
@@ -102,55 +101,54 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
102
101
  handlerArgs.push(args);
103
102
  }
104
103
  }
105
- /** Connect to server */
106
- for (const connectionOptions of this._config.connection) {
107
- const connection = await amqplib_1.default.connect(connectionOptions).catch(e => {
108
- e.message = 'Unable to connect to RabbitMQ server';
109
- throw new Error('Unable to connect to RabbitMQ server (' +
110
- (typeof connectionOptions == 'object'
111
- ? connectionOptions.hostname + ':' + connectionOptions.port
112
- : connectionOptions) +
113
- ')',
114
- // @ts-ignore
115
- e);
116
- });
117
- this._connections.push(connection);
118
- const hostname = typeof connectionOptions === 'object'
119
- ? connectionOptions.hostname
120
- : connectionOptions;
121
- this.logger?.info?.(`Connected to ${hostname}`);
122
- /** Subscribe to channels */
123
- for (const args of handlerArgs) {
124
- /** Create channel per operation */
125
- const channel = await connection.createChannel();
126
- for (const topic of args.topics) {
127
- const opts = this._config.queues?.[topic];
128
- if (opts)
129
- await channel.assertQueue(topic, opts);
104
+ const connectionOptions = typeof this._config.connection === 'string'
105
+ ? {
106
+ urls: [this._config.connection],
107
+ }
108
+ : Array.isArray(this._config.connection)
109
+ ? {
110
+ urls: this._config.connection,
130
111
  }
131
- for (const topic of args.topics) {
132
- await channel.assertQueue(topic);
133
- await channel
134
- .consume(topic, async (msg) => {
135
- if (!msg)
136
- return;
137
- await this.emitAsync('message', msg).catch(() => undefined);
138
- try {
139
- await args.handler(channel, topic, msg);
140
- }
141
- catch (e) {
142
- this._emitError(e);
143
- }
144
- await this.emitAsync('message-finish', msg);
145
- },
146
- /** Consume options */
147
- args.operationConfig.consumer)
148
- .catch(e => {
112
+ : this._config.connection;
113
+ this._client = new amqp_connection_manager_1.AmqpConnectionManagerClass(connectionOptions.urls, connectionOptions);
114
+ this._client.connect().catch(e => {
115
+ e.message =
116
+ 'Unable to connect to RabbitMQ server at ' +
117
+ connectionOptions.urls +
118
+ '. ' +
119
+ e.message;
120
+ throw e;
121
+ });
122
+ this.logger?.info?.(`Connected RabbitMQ at ${connectionOptions.urls}`);
123
+ for (const args of handlerArgs) {
124
+ /** Create channel per operation */
125
+ const channel = this._client.createChannel();
126
+ for (const topic of args.topics) {
127
+ const opts = this._config.queues?.[topic];
128
+ if (opts)
129
+ await channel.assertQueue(topic, opts);
130
+ }
131
+ for (const topic of args.topics) {
132
+ await channel.assertQueue(topic);
133
+ await channel
134
+ .consume(topic, async (msg) => {
135
+ if (!msg)
136
+ return;
137
+ await this.emitAsync('message', msg, topic).catch(noOp);
138
+ try {
139
+ await args.handler(channel, topic, msg);
140
+ }
141
+ catch (e) {
149
142
  this._emitError(e);
150
- throw e;
151
- });
152
- this.logger?.info?.(`Subscribed to topic${args.topics.length > 1 ? 's' : ''} "${args.topics}"`);
153
- }
143
+ }
144
+ },
145
+ /** Consume options */
146
+ args.operationConfig.consumer)
147
+ .catch(e => {
148
+ this._emitError(e);
149
+ throw e;
150
+ });
151
+ this.logger?.info?.(`Subscribed to topic${args.topics.length > 1 ? 's' : ''} "${args.topics}"`);
154
152
  }
155
153
  }
156
154
  this._status = 'started';
@@ -164,8 +162,8 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
164
162
  * Closes all connections and stops the service
165
163
  */
166
164
  async close() {
167
- await Promise.all(this._connections.map(c => c.close()));
168
- this._connections = [];
165
+ await this._client?.close();
166
+ this._client = undefined;
169
167
  this._controllerInstances.clear();
170
168
  this._status = 'idle';
171
169
  }
@@ -188,7 +186,7 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
188
186
  return;
189
187
  const operationConfig = {
190
188
  consumer: {
191
- noAck: false,
189
+ noAck: true,
192
190
  },
193
191
  };
194
192
  if (this._config.defaults) {
@@ -276,11 +274,11 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
276
274
  this._emitError(e, context);
277
275
  return;
278
276
  }
279
- await this.emitAsync('before-execute', context);
277
+ await this.emitAsync('execute', context).catch(noOp);
280
278
  try {
281
279
  /** Call operation handler */
282
280
  const result = await operationHandler.call(instance, context);
283
- await this.emitAsync('after-execute', context, result);
281
+ await this.emitAsync('finish', context, result).catch(noOp);
284
282
  }
285
283
  catch (e) {
286
284
  this._emitError(e, context);
@@ -339,7 +337,9 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
339
337
  context.errors.push(error);
340
338
  context.errors = this._wrapExceptions(context.errors);
341
339
  if (context.listenerCount('error')) {
342
- await context.emitAsync('error', context.errors[0], context);
340
+ await context
341
+ .emitAsync('error', context.errors[0], context)
342
+ .catch(noOp);
343
343
  }
344
344
  if (logger?.error) {
345
345
  context.errors.forEach(err => logger.error(err));
@@ -348,7 +348,7 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
348
348
  else
349
349
  logger?.error(error);
350
350
  if (this.listenerCount('error'))
351
- this.emit('error', error, context);
351
+ this._emitError(error, context);
352
352
  })
353
353
  .catch(noOp);
354
354
  }
@@ -2,7 +2,7 @@ import zlib from 'node:zlib';
2
2
  import typeIs from '@browsery/type-is';
3
3
  import { OpraException, RPC_CONTROLLER_METADATA, RpcApi, } from '@opra/common';
4
4
  import { kAssetCache, PlatformAdapter } from '@opra/core';
5
- import amqplib from 'amqplib';
5
+ import { AmqpConnectionManagerClass, } from 'amqp-connection-manager';
6
6
  import { parse as parseContentType } from 'content-type';
7
7
  import iconv from 'iconv-lite';
8
8
  import { promisify } from 'util';
@@ -30,7 +30,6 @@ export class RabbitmqAdapter extends PlatformAdapter {
30
30
  constructor(document, config) {
31
31
  super(config);
32
32
  this._controllerInstances = new Map();
33
- this._connections = [];
34
33
  this._status = 'idle';
35
34
  this.protocol = 'rpc';
36
35
  this.platform = RabbitmqAdapter.PlatformName;
@@ -98,55 +97,54 @@ export class RabbitmqAdapter extends PlatformAdapter {
98
97
  handlerArgs.push(args);
99
98
  }
100
99
  }
101
- /** Connect to server */
102
- for (const connectionOptions of this._config.connection) {
103
- const connection = await amqplib.connect(connectionOptions).catch(e => {
104
- e.message = 'Unable to connect to RabbitMQ server';
105
- throw new Error('Unable to connect to RabbitMQ server (' +
106
- (typeof connectionOptions == 'object'
107
- ? connectionOptions.hostname + ':' + connectionOptions.port
108
- : connectionOptions) +
109
- ')',
110
- // @ts-ignore
111
- e);
112
- });
113
- this._connections.push(connection);
114
- const hostname = typeof connectionOptions === 'object'
115
- ? connectionOptions.hostname
116
- : connectionOptions;
117
- this.logger?.info?.(`Connected to ${hostname}`);
118
- /** Subscribe to channels */
119
- for (const args of handlerArgs) {
120
- /** Create channel per operation */
121
- const channel = await connection.createChannel();
122
- for (const topic of args.topics) {
123
- const opts = this._config.queues?.[topic];
124
- if (opts)
125
- await channel.assertQueue(topic, opts);
100
+ const connectionOptions = typeof this._config.connection === 'string'
101
+ ? {
102
+ urls: [this._config.connection],
103
+ }
104
+ : Array.isArray(this._config.connection)
105
+ ? {
106
+ urls: this._config.connection,
126
107
  }
127
- for (const topic of args.topics) {
128
- await channel.assertQueue(topic);
129
- await channel
130
- .consume(topic, async (msg) => {
131
- if (!msg)
132
- return;
133
- await this.emitAsync('message', msg).catch(() => undefined);
134
- try {
135
- await args.handler(channel, topic, msg);
136
- }
137
- catch (e) {
138
- this._emitError(e);
139
- }
140
- await this.emitAsync('message-finish', msg);
141
- },
142
- /** Consume options */
143
- args.operationConfig.consumer)
144
- .catch(e => {
108
+ : this._config.connection;
109
+ this._client = new AmqpConnectionManagerClass(connectionOptions.urls, connectionOptions);
110
+ this._client.connect().catch(e => {
111
+ e.message =
112
+ 'Unable to connect to RabbitMQ server at ' +
113
+ connectionOptions.urls +
114
+ '. ' +
115
+ e.message;
116
+ throw e;
117
+ });
118
+ this.logger?.info?.(`Connected RabbitMQ at ${connectionOptions.urls}`);
119
+ for (const args of handlerArgs) {
120
+ /** Create channel per operation */
121
+ const channel = this._client.createChannel();
122
+ for (const topic of args.topics) {
123
+ const opts = this._config.queues?.[topic];
124
+ if (opts)
125
+ await channel.assertQueue(topic, opts);
126
+ }
127
+ for (const topic of args.topics) {
128
+ await channel.assertQueue(topic);
129
+ await channel
130
+ .consume(topic, async (msg) => {
131
+ if (!msg)
132
+ return;
133
+ await this.emitAsync('message', msg, topic).catch(noOp);
134
+ try {
135
+ await args.handler(channel, topic, msg);
136
+ }
137
+ catch (e) {
145
138
  this._emitError(e);
146
- throw e;
147
- });
148
- this.logger?.info?.(`Subscribed to topic${args.topics.length > 1 ? 's' : ''} "${args.topics}"`);
149
- }
139
+ }
140
+ },
141
+ /** Consume options */
142
+ args.operationConfig.consumer)
143
+ .catch(e => {
144
+ this._emitError(e);
145
+ throw e;
146
+ });
147
+ this.logger?.info?.(`Subscribed to topic${args.topics.length > 1 ? 's' : ''} "${args.topics}"`);
150
148
  }
151
149
  }
152
150
  this._status = 'started';
@@ -160,8 +158,8 @@ export class RabbitmqAdapter extends PlatformAdapter {
160
158
  * Closes all connections and stops the service
161
159
  */
162
160
  async close() {
163
- await Promise.all(this._connections.map(c => c.close()));
164
- this._connections = [];
161
+ await this._client?.close();
162
+ this._client = undefined;
165
163
  this._controllerInstances.clear();
166
164
  this._status = 'idle';
167
165
  }
@@ -184,7 +182,7 @@ export class RabbitmqAdapter extends PlatformAdapter {
184
182
  return;
185
183
  const operationConfig = {
186
184
  consumer: {
187
- noAck: false,
185
+ noAck: true,
188
186
  },
189
187
  };
190
188
  if (this._config.defaults) {
@@ -272,11 +270,11 @@ export class RabbitmqAdapter extends PlatformAdapter {
272
270
  this._emitError(e, context);
273
271
  return;
274
272
  }
275
- await this.emitAsync('before-execute', context);
273
+ await this.emitAsync('execute', context).catch(noOp);
276
274
  try {
277
275
  /** Call operation handler */
278
276
  const result = await operationHandler.call(instance, context);
279
- await this.emitAsync('after-execute', context, result);
277
+ await this.emitAsync('finish', context, result).catch(noOp);
280
278
  }
281
279
  catch (e) {
282
280
  this._emitError(e, context);
@@ -335,7 +333,9 @@ export class RabbitmqAdapter extends PlatformAdapter {
335
333
  context.errors.push(error);
336
334
  context.errors = this._wrapExceptions(context.errors);
337
335
  if (context.listenerCount('error')) {
338
- await context.emitAsync('error', context.errors[0], context);
336
+ await context
337
+ .emitAsync('error', context.errors[0], context)
338
+ .catch(noOp);
339
339
  }
340
340
  if (logger?.error) {
341
341
  context.errors.forEach(err => logger.error(err));
@@ -344,7 +344,7 @@ export class RabbitmqAdapter extends PlatformAdapter {
344
344
  else
345
345
  logger?.error(error);
346
346
  if (this.listenerCount('error'))
347
- this.emit('error', error, context);
347
+ this._emitError(error, context);
348
348
  })
349
349
  .catch(noOp);
350
350
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opra/rabbitmq",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "Opra RabbitMQ package",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
@@ -8,13 +8,14 @@
8
8
  "@browsery/type-is": "^1.6.18-r8",
9
9
  "content-type": "^1.0.5",
10
10
  "iconv-lite": "^0.6.3",
11
- "node-events-async": "^1.0.0",
11
+ "node-events-async": "^1.1.1",
12
12
  "tslib": "^2.8.1",
13
13
  "valgen": "^5.15.0"
14
14
  },
15
15
  "peerDependencies": {
16
- "@opra/common": "^1.15.0",
17
- "@opra/core": "^1.15.0",
16
+ "@opra/common": "^1.16.0",
17
+ "@opra/core": "^1.16.0",
18
+ "amqp-connection-manager": "^4.1.14",
18
19
  "amqplib": "^0.10.7"
19
20
  },
20
21
  "type": "module",
@@ -1,6 +1,7 @@
1
1
  import { ApiDocument, OpraException, OpraSchema, RpcApi, RpcController, RpcOperation } from '@opra/common';
2
2
  import { PlatformAdapter } from '@opra/core';
3
- import amqplib, { Channel } from 'amqplib';
3
+ import { type AmqpConnectionManager, type AmqpConnectionManagerOptions, type ChannelWrapper, type ConnectionUrl } from 'amqp-connection-manager';
4
+ import amqplib from 'amqplib';
4
5
  import { ConsumeMessage } from 'amqplib/properties';
5
6
  import { RabbitmqContext } from './rabbitmq-context.js';
6
7
  export interface OperationConfig {
@@ -12,18 +13,18 @@ interface HandlerArguments {
12
13
  instance: any;
13
14
  operation: RpcOperation;
14
15
  operationConfig: OperationConfig;
15
- handler: (channel: Channel, queue: string, msg: ConsumeMessage | null) => void | Promise<void>;
16
+ handler: (channel: ChannelWrapper, queue: string, msg: ConsumeMessage | null) => void | Promise<void>;
16
17
  topics: string[];
17
18
  }
18
19
  /**
19
20
  *
20
21
  * @class RabbitmqAdapter
21
22
  */
22
- export declare class RabbitmqAdapter extends PlatformAdapter {
23
+ export declare class RabbitmqAdapter extends PlatformAdapter<RabbitmqAdapter.Events> {
23
24
  static readonly PlatformName = "rabbitmq";
24
25
  protected _config: RabbitmqAdapter.Config;
25
26
  protected _controllerInstances: Map<RpcController, any>;
26
- protected _connections: amqplib.ChannelModel[];
27
+ protected _client?: AmqpConnectionManager;
27
28
  protected _status: RabbitmqAdapter.Status;
28
29
  readonly protocol: OpraSchema.Transport;
29
30
  readonly platform = "rabbitmq";
@@ -71,9 +72,11 @@ export declare class RabbitmqAdapter extends PlatformAdapter {
71
72
  export declare namespace RabbitmqAdapter {
72
73
  type NextCallback = () => Promise<any>;
73
74
  type Status = 'idle' | 'starting' | 'started';
74
- type ConnectionOptions = amqplib.Options.Connect;
75
+ interface ConnectionOptions extends AmqpConnectionManagerOptions {
76
+ urls?: ConnectionUrl[];
77
+ }
75
78
  interface Config extends PlatformAdapter.Options {
76
- connection: (string | ConnectionOptions)[];
79
+ connection: string | string[] | ConnectionOptions;
77
80
  queues?: Record<string, amqplib.Options.AssertQueue>;
78
81
  defaults?: {
79
82
  consumer?: amqplib.Options.Consume;
@@ -98,5 +101,11 @@ export declare namespace RabbitmqAdapter {
98
101
  type IRabbitmqInterceptor = {
99
102
  intercept(context: RabbitmqContext, next: NextCallback): Promise<any>;
100
103
  };
104
+ interface Events {
105
+ error: [Error, RabbitmqContext | undefined];
106
+ execute: [RabbitmqContext];
107
+ finish: [RabbitmqContext, any];
108
+ message: [ConsumeMessage, string];
109
+ }
101
110
  }
102
111
  export {};
@@ -1,6 +1,6 @@
1
1
  import { OpraSchema, RpcController, RpcOperation } from '@opra/common';
2
2
  import { ExecutionContext } from '@opra/core';
3
- import type { Channel } from 'amqplib';
3
+ import type { ChannelWrapper } from 'amqp-connection-manager';
4
4
  import type { ConsumeMessage } from 'amqplib/properties';
5
5
  import type { AsyncEventEmitter } from 'node-events-async';
6
6
  import type { RabbitmqAdapter } from './rabbitmq-adapter';
@@ -18,7 +18,7 @@ export declare class RabbitmqContext extends ExecutionContext implements AsyncEv
18
18
  readonly operation?: RpcOperation;
19
19
  readonly operationHandler?: Function;
20
20
  readonly queue: string;
21
- readonly channel: Channel;
21
+ readonly channel: ChannelWrapper;
22
22
  readonly message: ConsumeMessage;
23
23
  readonly content: any;
24
24
  readonly headers: Record<string, any>;
@@ -27,15 +27,15 @@ export declare class RabbitmqContext extends ExecutionContext implements AsyncEv
27
27
  * @param init the context options
28
28
  */
29
29
  constructor(init: RabbitmqContext.Initiator);
30
- get properties(): import("amqplib").MessageProperties;
31
- get fields(): import("amqplib").ConsumeMessageFields;
30
+ get properties(): import("amqplib/properties").MessageProperties;
31
+ get fields(): import("amqplib/properties").ConsumeMessageFields;
32
32
  ack(): void;
33
33
  nack(): void;
34
34
  }
35
35
  export declare namespace RabbitmqContext {
36
36
  interface Initiator extends Omit<ExecutionContext.Initiator, 'document' | 'protocol' | 'documentNode'> {
37
37
  adapter: RabbitmqAdapter;
38
- channel: Channel;
38
+ channel: ChannelWrapper;
39
39
  controller?: RpcController;
40
40
  controllerInstance?: any;
41
41
  operation?: RpcOperation;