@opra/rabbitmq 1.16.1 → 1.17.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/cjs/config-builder.js +88 -0
- package/cjs/rabbitmq-adapter.js +80 -133
- package/cjs/rabbitmq-context.js +2 -20
- package/esm/config-builder.js +84 -0
- package/esm/rabbitmq-adapter.js +81 -134
- package/esm/rabbitmq-context.js +2 -20
- package/package.json +5 -7
- package/types/augmentation/opra-common.augmentation.d.ts +1 -1
- package/types/config-builder.d.ts +29 -0
- package/types/rabbitmq-adapter.d.ts +17 -40
- package/types/rabbitmq-context.d.ts +7 -11
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigBuilder = void 0;
|
|
4
|
+
const objects_1 = require("@jsopen/objects");
|
|
5
|
+
const common_1 = require("@opra/common");
|
|
6
|
+
const constants_js_1 = require("./constants.js");
|
|
7
|
+
class ConfigBuilder {
|
|
8
|
+
constructor(document, config) {
|
|
9
|
+
this.document = document;
|
|
10
|
+
this.config = config;
|
|
11
|
+
}
|
|
12
|
+
async build() {
|
|
13
|
+
this.controllerInstances = new Map();
|
|
14
|
+
this.handlerArgs = [];
|
|
15
|
+
this._prepareConnectionOptions();
|
|
16
|
+
/** Initialize consumers */
|
|
17
|
+
for (const controller of this.document.rpcApi.controllers.values()) {
|
|
18
|
+
let instance = controller.instance;
|
|
19
|
+
if (!instance && controller.ctor)
|
|
20
|
+
instance = new controller.ctor();
|
|
21
|
+
if (!instance)
|
|
22
|
+
continue;
|
|
23
|
+
this.controllerInstances.set(controller, instance);
|
|
24
|
+
/** Build HandlerData array */
|
|
25
|
+
for (const operation of controller.operations.values()) {
|
|
26
|
+
const consumerConfig = await this._getConsumerConfig(controller, instance, operation);
|
|
27
|
+
if (!consumerConfig)
|
|
28
|
+
continue;
|
|
29
|
+
const args = {
|
|
30
|
+
// consumer: null as any,
|
|
31
|
+
controller,
|
|
32
|
+
instance,
|
|
33
|
+
operation,
|
|
34
|
+
consumerConfig,
|
|
35
|
+
// handler: null as any,
|
|
36
|
+
topics: (Array.isArray(operation.channel)
|
|
37
|
+
? operation.channel
|
|
38
|
+
: [operation.channel]).map(String),
|
|
39
|
+
};
|
|
40
|
+
this.handlerArgs.push(args);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
_prepareConnectionOptions() {
|
|
45
|
+
this.connectionOptions = {};
|
|
46
|
+
if (Array.isArray(this.config.connection))
|
|
47
|
+
this.connectionOptions.urls = this.config.connection;
|
|
48
|
+
else if (typeof this.config.connection === 'object') {
|
|
49
|
+
(0, objects_1.merge)(this.connectionOptions, this.config.connection, { deep: true });
|
|
50
|
+
}
|
|
51
|
+
else
|
|
52
|
+
this.connectionOptions.urls = [
|
|
53
|
+
this.config.connection || 'amqp://guest:guest@localhost:5672',
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @param controller
|
|
59
|
+
* @param instance
|
|
60
|
+
* @param operation
|
|
61
|
+
* @protected
|
|
62
|
+
*/
|
|
63
|
+
async _getConsumerConfig(controller, instance, operation) {
|
|
64
|
+
if (typeof instance[operation.name] !== 'function')
|
|
65
|
+
return;
|
|
66
|
+
const proto = controller.ctor?.prototype || Object.getPrototypeOf(instance);
|
|
67
|
+
if (Reflect.hasMetadata(common_1.RPC_CONTROLLER_METADATA, proto, operation.name))
|
|
68
|
+
return;
|
|
69
|
+
const operationConfig = {};
|
|
70
|
+
if (this.config.defaults) {
|
|
71
|
+
if (this.config.defaults.consumer) {
|
|
72
|
+
(0, objects_1.merge)(operationConfig, this.config.defaults.consumer, { deep: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
let metadata = Reflect.getMetadata(constants_js_1.RMQ_OPERATION_METADATA, proto, operation.name);
|
|
76
|
+
if (!metadata) {
|
|
77
|
+
const configResolver = Reflect.getMetadata(constants_js_1.RMQ_OPERATION_METADATA_RESOLVER, proto, operation.name);
|
|
78
|
+
if (configResolver) {
|
|
79
|
+
metadata = await configResolver();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (metadata && typeof metadata === 'object') {
|
|
83
|
+
(0, objects_1.merge)(operationConfig, metadata, { deep: true });
|
|
84
|
+
}
|
|
85
|
+
return operationConfig;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.ConfigBuilder = ConfigBuilder;
|
package/cjs/rabbitmq-adapter.js
CHANGED
|
@@ -6,12 +6,12 @@ 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 amqp_connection_manager_1 = require("amqp-connection-manager");
|
|
10
9
|
const content_type_1 = require("content-type");
|
|
11
10
|
const iconv_lite_1 = tslib_1.__importDefault(require("iconv-lite"));
|
|
11
|
+
const rabbit = tslib_1.__importStar(require("rabbitmq-client"));
|
|
12
12
|
const util_1 = require("util");
|
|
13
13
|
const valgen_1 = require("valgen");
|
|
14
|
-
const
|
|
14
|
+
const config_builder_js_1 = require("./config-builder.js");
|
|
15
15
|
const rabbitmq_context_js_1 = require("./rabbitmq-context.js");
|
|
16
16
|
const gunzipAsync = (0, util_1.promisify)(node_zlib_1.default.gunzip);
|
|
17
17
|
const deflateAsync = (0, util_1.promisify)(node_zlib_1.default.deflate);
|
|
@@ -34,6 +34,7 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
34
34
|
constructor(document, config) {
|
|
35
35
|
super(config);
|
|
36
36
|
this._controllerInstances = new Map();
|
|
37
|
+
this._consumers = [];
|
|
37
38
|
this._status = 'idle';
|
|
38
39
|
this.protocol = 'rpc';
|
|
39
40
|
this.platform = RabbitmqAdapter.PlatformName;
|
|
@@ -43,7 +44,6 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
43
44
|
this.document.api.platform === RabbitmqAdapter.PlatformName)) {
|
|
44
45
|
throw new TypeError(`The document doesn't expose a RabbitMQ Api`);
|
|
45
46
|
}
|
|
46
|
-
// this._config = config;
|
|
47
47
|
this.interceptors = [...(config.interceptors || [])];
|
|
48
48
|
globalErrorTypes.forEach(type => {
|
|
49
49
|
process.on(type, e => {
|
|
@@ -58,6 +58,9 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
58
58
|
get api() {
|
|
59
59
|
return this.document.rpcApi;
|
|
60
60
|
}
|
|
61
|
+
get connection() {
|
|
62
|
+
return this._connection;
|
|
63
|
+
}
|
|
61
64
|
get scope() {
|
|
62
65
|
return this._config.scope;
|
|
63
66
|
}
|
|
@@ -71,98 +74,69 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
71
74
|
if (this.status !== 'idle')
|
|
72
75
|
return;
|
|
73
76
|
this._status = 'starting';
|
|
74
|
-
const handlerArgs = [];
|
|
77
|
+
// const handlerArgs: HandlerArguments[] = [];
|
|
78
|
+
const configBuilder = new config_builder_js_1.ConfigBuilder(this.document, this._config);
|
|
79
|
+
await configBuilder.build();
|
|
80
|
+
this._connection = new rabbit.Connection(configBuilder.connectionOptions);
|
|
75
81
|
try {
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!instance && controller.ctor)
|
|
80
|
-
instance = new controller.ctor();
|
|
81
|
-
if (!instance)
|
|
82
|
-
continue;
|
|
83
|
-
this._controllerInstances.set(controller, instance);
|
|
84
|
-
/** Build HandlerData array */
|
|
85
|
-
for (const operation of controller.operations.values()) {
|
|
86
|
-
const operationConfig = await this._getOperationConfig(controller, instance, operation);
|
|
87
|
-
if (!operationConfig)
|
|
88
|
-
continue;
|
|
89
|
-
const args = {
|
|
90
|
-
consumer: null,
|
|
91
|
-
controller,
|
|
92
|
-
instance,
|
|
93
|
-
operation,
|
|
94
|
-
operationConfig,
|
|
95
|
-
handler: null,
|
|
96
|
-
topics: (Array.isArray(operation.channel)
|
|
97
|
-
? operation.channel
|
|
98
|
-
: [operation.channel]).map(String),
|
|
99
|
-
};
|
|
100
|
-
this._createHandler(args);
|
|
101
|
-
handlerArgs.push(args);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
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,
|
|
111
|
-
}
|
|
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;
|
|
82
|
+
/** Establish connection */
|
|
83
|
+
await this._connection.onConnect().catch(e => {
|
|
84
|
+
e.message = `RabbitMQ connection error. ${e.message}`;
|
|
120
85
|
throw e;
|
|
121
86
|
});
|
|
122
|
-
this.logger?.info?.(`Connected RabbitMQ at ${connectionOptions.urls}`);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
/** Consume options */
|
|
143
|
-
args.operationConfig.consumer)
|
|
144
|
-
.catch(e => {
|
|
87
|
+
this.logger?.info?.(`Connected RabbitMQ at ${configBuilder.connectionOptions.urls}`);
|
|
88
|
+
/** Subscribe to queues */
|
|
89
|
+
const promises = [];
|
|
90
|
+
for (const args of configBuilder.handlerArgs) {
|
|
91
|
+
for (const queue of args.topics) {
|
|
92
|
+
const handler = this._createHandler(args);
|
|
93
|
+
promises.push(new Promise((resolve, reject) => {
|
|
94
|
+
const consumer = this._connection.createConsumer({
|
|
95
|
+
...args.consumerConfig,
|
|
96
|
+
queueOptions: {
|
|
97
|
+
...args.consumerConfig.queueOptions,
|
|
98
|
+
durable: args.consumerConfig.queueOptions?.durable ?? true,
|
|
99
|
+
},
|
|
100
|
+
queue,
|
|
101
|
+
}, async (msg, reply) => {
|
|
102
|
+
await this.emitAsync('message', msg, queue).catch(noOp);
|
|
103
|
+
try {
|
|
104
|
+
await handler(consumer, queue, msg, reply);
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
145
107
|
this._emitError(e);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
this._consumers.push(consumer);
|
|
111
|
+
consumer.on('ready', () => {
|
|
112
|
+
this.logger?.info?.(`Subscribed to topic "${queue}"`);
|
|
113
|
+
resolve();
|
|
114
|
+
});
|
|
115
|
+
consumer.on('error', (err) => {
|
|
116
|
+
err.message = `Consumer error (${queue})". ${err.message}`;
|
|
117
|
+
err.queue = queue;
|
|
118
|
+
reject(err);
|
|
119
|
+
});
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
152
122
|
}
|
|
123
|
+
await Promise.all(promises);
|
|
153
124
|
this._status = 'started';
|
|
154
125
|
}
|
|
155
|
-
catch (
|
|
126
|
+
catch (err) {
|
|
127
|
+
this._emitError(err);
|
|
156
128
|
await this.close();
|
|
157
|
-
throw e;
|
|
158
129
|
}
|
|
159
130
|
}
|
|
160
131
|
/**
|
|
161
132
|
* Closes all connections and stops the service
|
|
162
133
|
*/
|
|
163
134
|
async close() {
|
|
164
|
-
|
|
165
|
-
|
|
135
|
+
if (this._consumers.length)
|
|
136
|
+
await Promise.allSettled(this._consumers.map(consumer => consumer.close()));
|
|
137
|
+
await this._connection?.close();
|
|
138
|
+
this._connection = undefined;
|
|
139
|
+
this._consumers = [];
|
|
166
140
|
this._controllerInstances.clear();
|
|
167
141
|
this._status = 'idle';
|
|
168
142
|
}
|
|
@@ -170,43 +144,6 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
170
144
|
const controller = this.api.findController(controllerPath);
|
|
171
145
|
return controller && this._controllerInstances.get(controller);
|
|
172
146
|
}
|
|
173
|
-
/**
|
|
174
|
-
*
|
|
175
|
-
* @param controller
|
|
176
|
-
* @param instance
|
|
177
|
-
* @param operation
|
|
178
|
-
* @protected
|
|
179
|
-
*/
|
|
180
|
-
async _getOperationConfig(controller, instance, operation) {
|
|
181
|
-
if (typeof instance[operation.name] !== 'function')
|
|
182
|
-
return;
|
|
183
|
-
const proto = controller.ctor?.prototype || Object.getPrototypeOf(instance);
|
|
184
|
-
if (Reflect.hasMetadata(common_1.RPC_CONTROLLER_METADATA, proto, operation.name))
|
|
185
|
-
return;
|
|
186
|
-
const operationConfig = {
|
|
187
|
-
consumer: {
|
|
188
|
-
noAck: true,
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
if (this._config.defaults) {
|
|
192
|
-
if (this._config.defaults.consumer) {
|
|
193
|
-
Object.assign(operationConfig.consumer, this._config.defaults.consumer);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
let metadata = Reflect.getMetadata(constants_js_1.RMQ_OPERATION_METADATA, proto, operation.name);
|
|
197
|
-
if (!metadata) {
|
|
198
|
-
const configResolver = Reflect.getMetadata(constants_js_1.RMQ_OPERATION_METADATA_RESOLVER, proto, operation.name);
|
|
199
|
-
if (configResolver) {
|
|
200
|
-
metadata = await configResolver();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if (metadata) {
|
|
204
|
-
if (typeof metadata.consumer === 'object') {
|
|
205
|
-
Object.assign(operationConfig.consumer, metadata.consumer);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return operationConfig;
|
|
209
|
-
}
|
|
210
147
|
/**
|
|
211
148
|
*
|
|
212
149
|
* @param args
|
|
@@ -230,10 +167,17 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
230
167
|
this[core_1.kAssetCache].set(header, 'decode', decode);
|
|
231
168
|
}
|
|
232
169
|
});
|
|
233
|
-
|
|
170
|
+
return async (consumer, queue, message, _reply) => {
|
|
234
171
|
if (!message)
|
|
235
172
|
return;
|
|
236
173
|
const operationHandler = instance[operation.name];
|
|
174
|
+
let replyCalled = false;
|
|
175
|
+
const reply = async (body, envelope) => {
|
|
176
|
+
if (replyCalled)
|
|
177
|
+
return;
|
|
178
|
+
replyCalled = true;
|
|
179
|
+
return _reply(body, envelope);
|
|
180
|
+
};
|
|
237
181
|
const headers = {};
|
|
238
182
|
/** Create context */
|
|
239
183
|
const context = new rabbitmq_context_js_1.RabbitmqContext({
|
|
@@ -244,10 +188,11 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
244
188
|
operation,
|
|
245
189
|
operationHandler,
|
|
246
190
|
queue,
|
|
247
|
-
|
|
191
|
+
consumer,
|
|
248
192
|
message,
|
|
249
193
|
content: undefined,
|
|
250
194
|
headers,
|
|
195
|
+
reply,
|
|
251
196
|
});
|
|
252
197
|
try {
|
|
253
198
|
/** Parse and decode `payload` */
|
|
@@ -259,8 +204,8 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
259
204
|
}
|
|
260
205
|
// message.properties.
|
|
261
206
|
/** Parse and decode `headers` */
|
|
262
|
-
if (message.
|
|
263
|
-
for (const [k, v] of Object.entries(message.
|
|
207
|
+
if (message.headers) {
|
|
208
|
+
for (const [k, v] of Object.entries(message.headers)) {
|
|
264
209
|
const header = operation.findHeader(k);
|
|
265
210
|
const decode = this[core_1.kAssetCache].get(header, 'decode') || valgen_1.vg.isAny();
|
|
266
211
|
headers[k] = decode(Buffer.isBuffer(v) ? v.toString() : v);
|
|
@@ -269,7 +214,6 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
269
214
|
context.content = content;
|
|
270
215
|
}
|
|
271
216
|
catch (e) {
|
|
272
|
-
context.ack();
|
|
273
217
|
this._emitError(e, context);
|
|
274
218
|
return;
|
|
275
219
|
}
|
|
@@ -277,6 +221,8 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
277
221
|
try {
|
|
278
222
|
/** Call operation handler */
|
|
279
223
|
const result = await operationHandler.call(instance, context);
|
|
224
|
+
if (result !== undefined)
|
|
225
|
+
await reply(result);
|
|
280
226
|
await this.emitAsync('finish', context, result).catch(noOp);
|
|
281
227
|
}
|
|
282
228
|
catch (e) {
|
|
@@ -285,11 +231,13 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
285
231
|
};
|
|
286
232
|
}
|
|
287
233
|
async _parseContent(msg) {
|
|
288
|
-
if (!msg.
|
|
234
|
+
if (!Buffer.isBuffer(msg.body))
|
|
235
|
+
return msg.body;
|
|
236
|
+
if (!msg.body?.length)
|
|
289
237
|
return;
|
|
290
|
-
let content = msg.
|
|
291
|
-
if (msg.
|
|
292
|
-
switch (msg.
|
|
238
|
+
let content = msg.body;
|
|
239
|
+
if (msg.contentEncoding) {
|
|
240
|
+
switch (msg.contentEncoding) {
|
|
293
241
|
case 'gzip':
|
|
294
242
|
case 'x-gzip': {
|
|
295
243
|
content = await gunzipAsync(content);
|
|
@@ -315,12 +263,11 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
315
263
|
}
|
|
316
264
|
}
|
|
317
265
|
}
|
|
318
|
-
const mediaType = msg.
|
|
319
|
-
(0, content_type_1.parse)(msg.
|
|
320
|
-
|
|
321
|
-
if (
|
|
322
|
-
charset = 'utf-8';
|
|
323
|
-
if (charset) {
|
|
266
|
+
const mediaType = msg.contentType
|
|
267
|
+
? (0, content_type_1.parse)(msg.contentType || '')
|
|
268
|
+
: undefined;
|
|
269
|
+
if (mediaType && type_is_1.default.is(mediaType.type, ['json', 'xml', 'txt'])) {
|
|
270
|
+
const charset = (mediaType.parameters.charset || '').toLowerCase() || 'utf-8';
|
|
324
271
|
content = iconv_lite_1.default.decode(content, charset);
|
|
325
272
|
if (type_is_1.default.is(mediaType.type, ['json']))
|
|
326
273
|
return JSON.parse(content);
|
package/cjs/rabbitmq-context.js
CHANGED
|
@@ -18,7 +18,6 @@ class RabbitmqContext extends core_1.ExecutionContext {
|
|
|
18
18
|
documentNode: init.controller?.node,
|
|
19
19
|
protocol: 'rpc',
|
|
20
20
|
});
|
|
21
|
-
this._ackSent = false;
|
|
22
21
|
this.adapter = init.adapter;
|
|
23
22
|
this.platform = init.adapter.platform;
|
|
24
23
|
this.protocol = 'rpc';
|
|
@@ -30,29 +29,12 @@ class RabbitmqContext extends core_1.ExecutionContext {
|
|
|
30
29
|
this.operation = init.operation;
|
|
31
30
|
if (init.operationHandler)
|
|
32
31
|
this.operationHandler = init.operationHandler;
|
|
33
|
-
this.
|
|
32
|
+
this.consumer = init.consumer;
|
|
34
33
|
this.queue = init.queue;
|
|
35
34
|
this.message = init.message;
|
|
36
35
|
this.headers = init.headers || {};
|
|
37
36
|
this.content = init.content;
|
|
38
|
-
|
|
39
|
-
get properties() {
|
|
40
|
-
return this.message.properties;
|
|
41
|
-
}
|
|
42
|
-
get fields() {
|
|
43
|
-
return this.message.fields;
|
|
44
|
-
}
|
|
45
|
-
ack() {
|
|
46
|
-
if (this._ackSent)
|
|
47
|
-
return;
|
|
48
|
-
this._ackSent = true;
|
|
49
|
-
this.channel.ack(this.message);
|
|
50
|
-
}
|
|
51
|
-
nack() {
|
|
52
|
-
if (this._ackSent)
|
|
53
|
-
return;
|
|
54
|
-
this._ackSent = true;
|
|
55
|
-
this.channel.nack(this.message);
|
|
37
|
+
this.reply = init.reply;
|
|
56
38
|
}
|
|
57
39
|
}
|
|
58
40
|
exports.RabbitmqContext = RabbitmqContext;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { merge } from '@jsopen/objects';
|
|
2
|
+
import { RPC_CONTROLLER_METADATA, } from '@opra/common';
|
|
3
|
+
import { RMQ_OPERATION_METADATA, RMQ_OPERATION_METADATA_RESOLVER, } from './constants.js';
|
|
4
|
+
export class ConfigBuilder {
|
|
5
|
+
constructor(document, config) {
|
|
6
|
+
this.document = document;
|
|
7
|
+
this.config = config;
|
|
8
|
+
}
|
|
9
|
+
async build() {
|
|
10
|
+
this.controllerInstances = new Map();
|
|
11
|
+
this.handlerArgs = [];
|
|
12
|
+
this._prepareConnectionOptions();
|
|
13
|
+
/** Initialize consumers */
|
|
14
|
+
for (const controller of this.document.rpcApi.controllers.values()) {
|
|
15
|
+
let instance = controller.instance;
|
|
16
|
+
if (!instance && controller.ctor)
|
|
17
|
+
instance = new controller.ctor();
|
|
18
|
+
if (!instance)
|
|
19
|
+
continue;
|
|
20
|
+
this.controllerInstances.set(controller, instance);
|
|
21
|
+
/** Build HandlerData array */
|
|
22
|
+
for (const operation of controller.operations.values()) {
|
|
23
|
+
const consumerConfig = await this._getConsumerConfig(controller, instance, operation);
|
|
24
|
+
if (!consumerConfig)
|
|
25
|
+
continue;
|
|
26
|
+
const args = {
|
|
27
|
+
// consumer: null as any,
|
|
28
|
+
controller,
|
|
29
|
+
instance,
|
|
30
|
+
operation,
|
|
31
|
+
consumerConfig,
|
|
32
|
+
// handler: null as any,
|
|
33
|
+
topics: (Array.isArray(operation.channel)
|
|
34
|
+
? operation.channel
|
|
35
|
+
: [operation.channel]).map(String),
|
|
36
|
+
};
|
|
37
|
+
this.handlerArgs.push(args);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
_prepareConnectionOptions() {
|
|
42
|
+
this.connectionOptions = {};
|
|
43
|
+
if (Array.isArray(this.config.connection))
|
|
44
|
+
this.connectionOptions.urls = this.config.connection;
|
|
45
|
+
else if (typeof this.config.connection === 'object') {
|
|
46
|
+
merge(this.connectionOptions, this.config.connection, { deep: true });
|
|
47
|
+
}
|
|
48
|
+
else
|
|
49
|
+
this.connectionOptions.urls = [
|
|
50
|
+
this.config.connection || 'amqp://guest:guest@localhost:5672',
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
*
|
|
55
|
+
* @param controller
|
|
56
|
+
* @param instance
|
|
57
|
+
* @param operation
|
|
58
|
+
* @protected
|
|
59
|
+
*/
|
|
60
|
+
async _getConsumerConfig(controller, instance, operation) {
|
|
61
|
+
if (typeof instance[operation.name] !== 'function')
|
|
62
|
+
return;
|
|
63
|
+
const proto = controller.ctor?.prototype || Object.getPrototypeOf(instance);
|
|
64
|
+
if (Reflect.hasMetadata(RPC_CONTROLLER_METADATA, proto, operation.name))
|
|
65
|
+
return;
|
|
66
|
+
const operationConfig = {};
|
|
67
|
+
if (this.config.defaults) {
|
|
68
|
+
if (this.config.defaults.consumer) {
|
|
69
|
+
merge(operationConfig, this.config.defaults.consumer, { deep: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let metadata = Reflect.getMetadata(RMQ_OPERATION_METADATA, proto, operation.name);
|
|
73
|
+
if (!metadata) {
|
|
74
|
+
const configResolver = Reflect.getMetadata(RMQ_OPERATION_METADATA_RESOLVER, proto, operation.name);
|
|
75
|
+
if (configResolver) {
|
|
76
|
+
metadata = await configResolver();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (metadata && typeof metadata === 'object') {
|
|
80
|
+
merge(operationConfig, metadata, { deep: true });
|
|
81
|
+
}
|
|
82
|
+
return operationConfig;
|
|
83
|
+
}
|
|
84
|
+
}
|
package/esm/rabbitmq-adapter.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import zlib from 'node:zlib';
|
|
2
2
|
import typeIs from '@browsery/type-is';
|
|
3
|
-
import { OpraException,
|
|
3
|
+
import { OpraException, RpcApi, } from '@opra/common';
|
|
4
4
|
import { kAssetCache, PlatformAdapter } from '@opra/core';
|
|
5
|
-
import { AmqpConnectionManagerClass, } from 'amqp-connection-manager';
|
|
6
5
|
import { parse as parseContentType } from 'content-type';
|
|
7
6
|
import iconv from 'iconv-lite';
|
|
7
|
+
import * as rabbit from 'rabbitmq-client';
|
|
8
8
|
import { promisify } from 'util';
|
|
9
9
|
import { vg } from 'valgen';
|
|
10
|
-
import {
|
|
10
|
+
import { ConfigBuilder } from './config-builder.js';
|
|
11
11
|
import { RabbitmqContext } from './rabbitmq-context.js';
|
|
12
12
|
const gunzipAsync = promisify(zlib.gunzip);
|
|
13
13
|
const deflateAsync = promisify(zlib.deflate);
|
|
@@ -30,6 +30,7 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
30
30
|
constructor(document, config) {
|
|
31
31
|
super(config);
|
|
32
32
|
this._controllerInstances = new Map();
|
|
33
|
+
this._consumers = [];
|
|
33
34
|
this._status = 'idle';
|
|
34
35
|
this.protocol = 'rpc';
|
|
35
36
|
this.platform = RabbitmqAdapter.PlatformName;
|
|
@@ -39,7 +40,6 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
39
40
|
this.document.api.platform === RabbitmqAdapter.PlatformName)) {
|
|
40
41
|
throw new TypeError(`The document doesn't expose a RabbitMQ Api`);
|
|
41
42
|
}
|
|
42
|
-
// this._config = config;
|
|
43
43
|
this.interceptors = [...(config.interceptors || [])];
|
|
44
44
|
globalErrorTypes.forEach(type => {
|
|
45
45
|
process.on(type, e => {
|
|
@@ -54,6 +54,9 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
54
54
|
get api() {
|
|
55
55
|
return this.document.rpcApi;
|
|
56
56
|
}
|
|
57
|
+
get connection() {
|
|
58
|
+
return this._connection;
|
|
59
|
+
}
|
|
57
60
|
get scope() {
|
|
58
61
|
return this._config.scope;
|
|
59
62
|
}
|
|
@@ -67,98 +70,69 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
67
70
|
if (this.status !== 'idle')
|
|
68
71
|
return;
|
|
69
72
|
this._status = 'starting';
|
|
70
|
-
const handlerArgs = [];
|
|
73
|
+
// const handlerArgs: HandlerArguments[] = [];
|
|
74
|
+
const configBuilder = new ConfigBuilder(this.document, this._config);
|
|
75
|
+
await configBuilder.build();
|
|
76
|
+
this._connection = new rabbit.Connection(configBuilder.connectionOptions);
|
|
71
77
|
try {
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!instance && controller.ctor)
|
|
76
|
-
instance = new controller.ctor();
|
|
77
|
-
if (!instance)
|
|
78
|
-
continue;
|
|
79
|
-
this._controllerInstances.set(controller, instance);
|
|
80
|
-
/** Build HandlerData array */
|
|
81
|
-
for (const operation of controller.operations.values()) {
|
|
82
|
-
const operationConfig = await this._getOperationConfig(controller, instance, operation);
|
|
83
|
-
if (!operationConfig)
|
|
84
|
-
continue;
|
|
85
|
-
const args = {
|
|
86
|
-
consumer: null,
|
|
87
|
-
controller,
|
|
88
|
-
instance,
|
|
89
|
-
operation,
|
|
90
|
-
operationConfig,
|
|
91
|
-
handler: null,
|
|
92
|
-
topics: (Array.isArray(operation.channel)
|
|
93
|
-
? operation.channel
|
|
94
|
-
: [operation.channel]).map(String),
|
|
95
|
-
};
|
|
96
|
-
this._createHandler(args);
|
|
97
|
-
handlerArgs.push(args);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
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,
|
|
107
|
-
}
|
|
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;
|
|
78
|
+
/** Establish connection */
|
|
79
|
+
await this._connection.onConnect().catch(e => {
|
|
80
|
+
e.message = `RabbitMQ connection error. ${e.message}`;
|
|
116
81
|
throw e;
|
|
117
82
|
});
|
|
118
|
-
this.logger?.info?.(`Connected RabbitMQ at ${connectionOptions.urls}`);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
/** Consume options */
|
|
139
|
-
args.operationConfig.consumer)
|
|
140
|
-
.catch(e => {
|
|
83
|
+
this.logger?.info?.(`Connected RabbitMQ at ${configBuilder.connectionOptions.urls}`);
|
|
84
|
+
/** Subscribe to queues */
|
|
85
|
+
const promises = [];
|
|
86
|
+
for (const args of configBuilder.handlerArgs) {
|
|
87
|
+
for (const queue of args.topics) {
|
|
88
|
+
const handler = this._createHandler(args);
|
|
89
|
+
promises.push(new Promise((resolve, reject) => {
|
|
90
|
+
const consumer = this._connection.createConsumer({
|
|
91
|
+
...args.consumerConfig,
|
|
92
|
+
queueOptions: {
|
|
93
|
+
...args.consumerConfig.queueOptions,
|
|
94
|
+
durable: args.consumerConfig.queueOptions?.durable ?? true,
|
|
95
|
+
},
|
|
96
|
+
queue,
|
|
97
|
+
}, async (msg, reply) => {
|
|
98
|
+
await this.emitAsync('message', msg, queue).catch(noOp);
|
|
99
|
+
try {
|
|
100
|
+
await handler(consumer, queue, msg, reply);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
141
103
|
this._emitError(e);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
this._consumers.push(consumer);
|
|
107
|
+
consumer.on('ready', () => {
|
|
108
|
+
this.logger?.info?.(`Subscribed to topic "${queue}"`);
|
|
109
|
+
resolve();
|
|
110
|
+
});
|
|
111
|
+
consumer.on('error', (err) => {
|
|
112
|
+
err.message = `Consumer error (${queue})". ${err.message}`;
|
|
113
|
+
err.queue = queue;
|
|
114
|
+
reject(err);
|
|
115
|
+
});
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
148
118
|
}
|
|
119
|
+
await Promise.all(promises);
|
|
149
120
|
this._status = 'started';
|
|
150
121
|
}
|
|
151
|
-
catch (
|
|
122
|
+
catch (err) {
|
|
123
|
+
this._emitError(err);
|
|
152
124
|
await this.close();
|
|
153
|
-
throw e;
|
|
154
125
|
}
|
|
155
126
|
}
|
|
156
127
|
/**
|
|
157
128
|
* Closes all connections and stops the service
|
|
158
129
|
*/
|
|
159
130
|
async close() {
|
|
160
|
-
|
|
161
|
-
|
|
131
|
+
if (this._consumers.length)
|
|
132
|
+
await Promise.allSettled(this._consumers.map(consumer => consumer.close()));
|
|
133
|
+
await this._connection?.close();
|
|
134
|
+
this._connection = undefined;
|
|
135
|
+
this._consumers = [];
|
|
162
136
|
this._controllerInstances.clear();
|
|
163
137
|
this._status = 'idle';
|
|
164
138
|
}
|
|
@@ -166,43 +140,6 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
166
140
|
const controller = this.api.findController(controllerPath);
|
|
167
141
|
return controller && this._controllerInstances.get(controller);
|
|
168
142
|
}
|
|
169
|
-
/**
|
|
170
|
-
*
|
|
171
|
-
* @param controller
|
|
172
|
-
* @param instance
|
|
173
|
-
* @param operation
|
|
174
|
-
* @protected
|
|
175
|
-
*/
|
|
176
|
-
async _getOperationConfig(controller, instance, operation) {
|
|
177
|
-
if (typeof instance[operation.name] !== 'function')
|
|
178
|
-
return;
|
|
179
|
-
const proto = controller.ctor?.prototype || Object.getPrototypeOf(instance);
|
|
180
|
-
if (Reflect.hasMetadata(RPC_CONTROLLER_METADATA, proto, operation.name))
|
|
181
|
-
return;
|
|
182
|
-
const operationConfig = {
|
|
183
|
-
consumer: {
|
|
184
|
-
noAck: true,
|
|
185
|
-
},
|
|
186
|
-
};
|
|
187
|
-
if (this._config.defaults) {
|
|
188
|
-
if (this._config.defaults.consumer) {
|
|
189
|
-
Object.assign(operationConfig.consumer, this._config.defaults.consumer);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
let metadata = Reflect.getMetadata(RMQ_OPERATION_METADATA, proto, operation.name);
|
|
193
|
-
if (!metadata) {
|
|
194
|
-
const configResolver = Reflect.getMetadata(RMQ_OPERATION_METADATA_RESOLVER, proto, operation.name);
|
|
195
|
-
if (configResolver) {
|
|
196
|
-
metadata = await configResolver();
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (metadata) {
|
|
200
|
-
if (typeof metadata.consumer === 'object') {
|
|
201
|
-
Object.assign(operationConfig.consumer, metadata.consumer);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return operationConfig;
|
|
205
|
-
}
|
|
206
143
|
/**
|
|
207
144
|
*
|
|
208
145
|
* @param args
|
|
@@ -226,10 +163,17 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
226
163
|
this[kAssetCache].set(header, 'decode', decode);
|
|
227
164
|
}
|
|
228
165
|
});
|
|
229
|
-
|
|
166
|
+
return async (consumer, queue, message, _reply) => {
|
|
230
167
|
if (!message)
|
|
231
168
|
return;
|
|
232
169
|
const operationHandler = instance[operation.name];
|
|
170
|
+
let replyCalled = false;
|
|
171
|
+
const reply = async (body, envelope) => {
|
|
172
|
+
if (replyCalled)
|
|
173
|
+
return;
|
|
174
|
+
replyCalled = true;
|
|
175
|
+
return _reply(body, envelope);
|
|
176
|
+
};
|
|
233
177
|
const headers = {};
|
|
234
178
|
/** Create context */
|
|
235
179
|
const context = new RabbitmqContext({
|
|
@@ -240,10 +184,11 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
240
184
|
operation,
|
|
241
185
|
operationHandler,
|
|
242
186
|
queue,
|
|
243
|
-
|
|
187
|
+
consumer,
|
|
244
188
|
message,
|
|
245
189
|
content: undefined,
|
|
246
190
|
headers,
|
|
191
|
+
reply,
|
|
247
192
|
});
|
|
248
193
|
try {
|
|
249
194
|
/** Parse and decode `payload` */
|
|
@@ -255,8 +200,8 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
255
200
|
}
|
|
256
201
|
// message.properties.
|
|
257
202
|
/** Parse and decode `headers` */
|
|
258
|
-
if (message.
|
|
259
|
-
for (const [k, v] of Object.entries(message.
|
|
203
|
+
if (message.headers) {
|
|
204
|
+
for (const [k, v] of Object.entries(message.headers)) {
|
|
260
205
|
const header = operation.findHeader(k);
|
|
261
206
|
const decode = this[kAssetCache].get(header, 'decode') || vg.isAny();
|
|
262
207
|
headers[k] = decode(Buffer.isBuffer(v) ? v.toString() : v);
|
|
@@ -265,7 +210,6 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
265
210
|
context.content = content;
|
|
266
211
|
}
|
|
267
212
|
catch (e) {
|
|
268
|
-
context.ack();
|
|
269
213
|
this._emitError(e, context);
|
|
270
214
|
return;
|
|
271
215
|
}
|
|
@@ -273,6 +217,8 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
273
217
|
try {
|
|
274
218
|
/** Call operation handler */
|
|
275
219
|
const result = await operationHandler.call(instance, context);
|
|
220
|
+
if (result !== undefined)
|
|
221
|
+
await reply(result);
|
|
276
222
|
await this.emitAsync('finish', context, result).catch(noOp);
|
|
277
223
|
}
|
|
278
224
|
catch (e) {
|
|
@@ -281,11 +227,13 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
281
227
|
};
|
|
282
228
|
}
|
|
283
229
|
async _parseContent(msg) {
|
|
284
|
-
if (!msg.
|
|
230
|
+
if (!Buffer.isBuffer(msg.body))
|
|
231
|
+
return msg.body;
|
|
232
|
+
if (!msg.body?.length)
|
|
285
233
|
return;
|
|
286
|
-
let content = msg.
|
|
287
|
-
if (msg.
|
|
288
|
-
switch (msg.
|
|
234
|
+
let content = msg.body;
|
|
235
|
+
if (msg.contentEncoding) {
|
|
236
|
+
switch (msg.contentEncoding) {
|
|
289
237
|
case 'gzip':
|
|
290
238
|
case 'x-gzip': {
|
|
291
239
|
content = await gunzipAsync(content);
|
|
@@ -311,12 +259,11 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
311
259
|
}
|
|
312
260
|
}
|
|
313
261
|
}
|
|
314
|
-
const mediaType = msg.
|
|
315
|
-
parseContentType(msg.
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
charset = 'utf-8';
|
|
319
|
-
if (charset) {
|
|
262
|
+
const mediaType = msg.contentType
|
|
263
|
+
? parseContentType(msg.contentType || '')
|
|
264
|
+
: undefined;
|
|
265
|
+
if (mediaType && typeIs.is(mediaType.type, ['json', 'xml', 'txt'])) {
|
|
266
|
+
const charset = (mediaType.parameters.charset || '').toLowerCase() || 'utf-8';
|
|
320
267
|
content = iconv.decode(content, charset);
|
|
321
268
|
if (typeIs.is(mediaType.type, ['json']))
|
|
322
269
|
return JSON.parse(content);
|
package/esm/rabbitmq-context.js
CHANGED
|
@@ -15,7 +15,6 @@ export class RabbitmqContext extends ExecutionContext {
|
|
|
15
15
|
documentNode: init.controller?.node,
|
|
16
16
|
protocol: 'rpc',
|
|
17
17
|
});
|
|
18
|
-
this._ackSent = false;
|
|
19
18
|
this.adapter = init.adapter;
|
|
20
19
|
this.platform = init.adapter.platform;
|
|
21
20
|
this.protocol = 'rpc';
|
|
@@ -27,28 +26,11 @@ export class RabbitmqContext extends ExecutionContext {
|
|
|
27
26
|
this.operation = init.operation;
|
|
28
27
|
if (init.operationHandler)
|
|
29
28
|
this.operationHandler = init.operationHandler;
|
|
30
|
-
this.
|
|
29
|
+
this.consumer = init.consumer;
|
|
31
30
|
this.queue = init.queue;
|
|
32
31
|
this.message = init.message;
|
|
33
32
|
this.headers = init.headers || {};
|
|
34
33
|
this.content = init.content;
|
|
35
|
-
|
|
36
|
-
get properties() {
|
|
37
|
-
return this.message.properties;
|
|
38
|
-
}
|
|
39
|
-
get fields() {
|
|
40
|
-
return this.message.fields;
|
|
41
|
-
}
|
|
42
|
-
ack() {
|
|
43
|
-
if (this._ackSent)
|
|
44
|
-
return;
|
|
45
|
-
this._ackSent = true;
|
|
46
|
-
this.channel.ack(this.message);
|
|
47
|
-
}
|
|
48
|
-
nack() {
|
|
49
|
-
if (this._ackSent)
|
|
50
|
-
return;
|
|
51
|
-
this._ackSent = true;
|
|
52
|
-
this.channel.nack(this.message);
|
|
34
|
+
this.reply = init.reply;
|
|
53
35
|
}
|
|
54
36
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/rabbitmq",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
4
4
|
"description": "Opra RabbitMQ package",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"dependencies": {
|
|
8
|
+
"@jsopen/objects": "^1.5.2",
|
|
8
9
|
"@browsery/type-is": "^1.6.18-r8",
|
|
9
10
|
"content-type": "^1.0.5",
|
|
10
11
|
"iconv-lite": "^0.6.3",
|
|
@@ -13,10 +14,9 @@
|
|
|
13
14
|
"valgen": "^5.15.0"
|
|
14
15
|
},
|
|
15
16
|
"peerDependencies": {
|
|
16
|
-
"@opra/common": "^1.
|
|
17
|
-
"@opra/core": "^1.
|
|
18
|
-
"
|
|
19
|
-
"amqplib": "^0.10.7"
|
|
17
|
+
"@opra/common": "^1.17.1",
|
|
18
|
+
"@opra/core": "^1.17.1",
|
|
19
|
+
"rabbitmq-client": ">=5.0.0 <6"
|
|
20
20
|
},
|
|
21
21
|
"type": "module",
|
|
22
22
|
"exports": {
|
|
@@ -55,8 +55,6 @@
|
|
|
55
55
|
"keywords": [
|
|
56
56
|
"opra",
|
|
57
57
|
"rabbitmq",
|
|
58
|
-
"amqp",
|
|
59
|
-
"amqplib",
|
|
60
58
|
"message",
|
|
61
59
|
"queue",
|
|
62
60
|
"consumer"
|
|
@@ -2,6 +2,6 @@ import '@opra/core';
|
|
|
2
2
|
import { RabbitmqAdapter } from '../rabbitmq-adapter.js';
|
|
3
3
|
declare module '@opra/common' {
|
|
4
4
|
interface RpcOperationDecorator {
|
|
5
|
-
RabbitMQ(config: RabbitmqAdapter.
|
|
5
|
+
RabbitMQ(config: RabbitmqAdapter.ConsumerConfig | (() => RabbitmqAdapter.ConsumerConfig | Promise<RabbitmqAdapter.ConsumerConfig>)): this;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ApiDocument, RpcController, RpcOperation } from '@opra/common';
|
|
2
|
+
import type { RabbitmqAdapter } from './rabbitmq-adapter.js';
|
|
3
|
+
export declare class ConfigBuilder {
|
|
4
|
+
readonly document: ApiDocument;
|
|
5
|
+
readonly config: RabbitmqAdapter.Config;
|
|
6
|
+
connectionOptions: RabbitmqAdapter.ConnectionOptions;
|
|
7
|
+
controllerInstances: Map<RpcController, any>;
|
|
8
|
+
handlerArgs: ConfigBuilder.OperationArguments[];
|
|
9
|
+
constructor(document: ApiDocument, config: RabbitmqAdapter.Config);
|
|
10
|
+
build(): Promise<void>;
|
|
11
|
+
protected _prepareConnectionOptions(): void;
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param controller
|
|
15
|
+
* @param instance
|
|
16
|
+
* @param operation
|
|
17
|
+
* @protected
|
|
18
|
+
*/
|
|
19
|
+
protected _getConsumerConfig(controller: RpcController, instance: any, operation: RpcOperation): Promise<RabbitmqAdapter.ConsumerConfig | undefined>;
|
|
20
|
+
}
|
|
21
|
+
export declare namespace ConfigBuilder {
|
|
22
|
+
interface OperationArguments {
|
|
23
|
+
controller: RpcController;
|
|
24
|
+
instance: any;
|
|
25
|
+
operation: RpcOperation;
|
|
26
|
+
consumerConfig: RabbitmqAdapter.ConsumerConfig;
|
|
27
|
+
topics: string[];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
import { ApiDocument, OpraException, OpraSchema, RpcApi, RpcController
|
|
1
|
+
import { ApiDocument, OpraException, OpraSchema, RpcApi, RpcController } from '@opra/common';
|
|
2
2
|
import { PlatformAdapter } from '@opra/core';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
import * as rabbit from 'rabbitmq-client';
|
|
4
|
+
import type { Envelope, MessageBody } from 'rabbitmq-client/lib/codec';
|
|
5
|
+
import { ConfigBuilder } from './config-builder.js';
|
|
6
6
|
import { RabbitmqContext } from './rabbitmq-context.js';
|
|
7
|
-
export interface OperationConfig {
|
|
8
|
-
consumer: amqplib.Options.Consume & {};
|
|
9
|
-
}
|
|
10
|
-
interface HandlerArguments {
|
|
11
|
-
consumer: amqplib.Options.Consume & {};
|
|
12
|
-
controller: RpcController;
|
|
13
|
-
instance: any;
|
|
14
|
-
operation: RpcOperation;
|
|
15
|
-
operationConfig: OperationConfig;
|
|
16
|
-
handler: (channel: ChannelWrapper, queue: string, msg: ConsumeMessage | null) => void | Promise<void>;
|
|
17
|
-
topics: string[];
|
|
18
|
-
}
|
|
19
7
|
/**
|
|
20
8
|
*
|
|
21
9
|
* @class RabbitmqAdapter
|
|
@@ -24,7 +12,8 @@ export declare class RabbitmqAdapter extends PlatformAdapter<RabbitmqAdapter.Eve
|
|
|
24
12
|
static readonly PlatformName = "rabbitmq";
|
|
25
13
|
protected _config: RabbitmqAdapter.Config;
|
|
26
14
|
protected _controllerInstances: Map<RpcController, any>;
|
|
27
|
-
protected
|
|
15
|
+
protected _connection?: rabbit.Connection;
|
|
16
|
+
protected _consumers: rabbit.Consumer[];
|
|
28
17
|
protected _status: RabbitmqAdapter.Status;
|
|
29
18
|
readonly protocol: OpraSchema.Transport;
|
|
30
19
|
readonly platform = "rabbitmq";
|
|
@@ -37,6 +26,7 @@ export declare class RabbitmqAdapter extends PlatformAdapter<RabbitmqAdapter.Eve
|
|
|
37
26
|
*/
|
|
38
27
|
constructor(document: ApiDocument, config: RabbitmqAdapter.Config);
|
|
39
28
|
get api(): RpcApi;
|
|
29
|
+
get connection(): rabbit.Connection | undefined;
|
|
40
30
|
get scope(): string | undefined;
|
|
41
31
|
get status(): RabbitmqAdapter.Status;
|
|
42
32
|
/**
|
|
@@ -48,21 +38,13 @@ export declare class RabbitmqAdapter extends PlatformAdapter<RabbitmqAdapter.Eve
|
|
|
48
38
|
*/
|
|
49
39
|
close(): Promise<void>;
|
|
50
40
|
getControllerInstance<T>(controllerPath: string): T | undefined;
|
|
51
|
-
/**
|
|
52
|
-
*
|
|
53
|
-
* @param controller
|
|
54
|
-
* @param instance
|
|
55
|
-
* @param operation
|
|
56
|
-
* @protected
|
|
57
|
-
*/
|
|
58
|
-
protected _getOperationConfig(controller: RpcController, instance: any, operation: RpcOperation): Promise<OperationConfig | undefined>;
|
|
59
41
|
/**
|
|
60
42
|
*
|
|
61
43
|
* @param args
|
|
62
44
|
* @protected
|
|
63
45
|
*/
|
|
64
|
-
protected _createHandler(args:
|
|
65
|
-
protected _parseContent(msg:
|
|
46
|
+
protected _createHandler(args: ConfigBuilder.OperationArguments): (consumer: rabbit.Consumer, queue: string, message: rabbit.AsyncMessage, _reply: RabbitmqAdapter.ReplyFunction) => Promise<void>;
|
|
47
|
+
protected _parseContent(msg: rabbit.AsyncMessage): Promise<any>;
|
|
66
48
|
protected _emitError(error: any, context?: RabbitmqContext): void;
|
|
67
49
|
protected _wrapExceptions(exceptions: any[]): OpraException[];
|
|
68
50
|
}
|
|
@@ -71,26 +53,22 @@ export declare class RabbitmqAdapter extends PlatformAdapter<RabbitmqAdapter.Eve
|
|
|
71
53
|
*/
|
|
72
54
|
export declare namespace RabbitmqAdapter {
|
|
73
55
|
type NextCallback = () => Promise<any>;
|
|
56
|
+
type ReplyFunction = (body: MessageBody, envelope?: Envelope) => Promise<void>;
|
|
74
57
|
type Status = 'idle' | 'starting' | 'started';
|
|
75
|
-
interface ConnectionOptions extends
|
|
76
|
-
urls?:
|
|
58
|
+
interface ConnectionOptions extends Pick<rabbit.ConnectionOptions, 'username' | 'password' | 'acquireTimeout' | 'connectionName' | 'connectionTimeout' | 'frameMax' | 'heartbeat' | 'maxChannels' | 'retryHigh' | 'retryLow' | 'noDelay' | 'tls' | 'socket'> {
|
|
59
|
+
urls?: string[];
|
|
60
|
+
}
|
|
61
|
+
interface ConsumerConfig extends Pick<rabbit.ConsumerProps, 'concurrency' | 'requeue' | 'qos' | 'queueOptions' | 'exchanges' | 'exchangeBindings' | 'exclusive'> {
|
|
77
62
|
}
|
|
78
63
|
interface Config extends PlatformAdapter.Options {
|
|
79
|
-
connection
|
|
80
|
-
queues?: Record<string, amqplib.Options.AssertQueue>;
|
|
64
|
+
connection?: string | string[] | ConnectionOptions;
|
|
81
65
|
defaults?: {
|
|
82
|
-
consumer?:
|
|
66
|
+
consumer?: ConsumerConfig;
|
|
83
67
|
};
|
|
84
68
|
scope?: string;
|
|
85
69
|
interceptors?: (InterceptorFunction | IRabbitmqInterceptor)[];
|
|
86
70
|
logExtra?: boolean;
|
|
87
71
|
}
|
|
88
|
-
interface OperationOptions {
|
|
89
|
-
/**
|
|
90
|
-
* ConsumerConfig
|
|
91
|
-
*/
|
|
92
|
-
consumer?: amqplib.Options.Consume;
|
|
93
|
-
}
|
|
94
72
|
/**
|
|
95
73
|
* @type InterceptorFunction
|
|
96
74
|
*/
|
|
@@ -105,7 +83,6 @@ export declare namespace RabbitmqAdapter {
|
|
|
105
83
|
error: [Error, RabbitmqContext | undefined];
|
|
106
84
|
execute: [RabbitmqContext];
|
|
107
85
|
finish: [RabbitmqContext, any];
|
|
108
|
-
message: [
|
|
86
|
+
message: [rabbit.AsyncMessage, string];
|
|
109
87
|
}
|
|
110
88
|
}
|
|
111
|
-
export {};
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { OpraSchema, RpcController, RpcOperation } from '@opra/common';
|
|
2
2
|
import { ExecutionContext } from '@opra/core';
|
|
3
|
-
import type { ChannelWrapper } from 'amqp-connection-manager';
|
|
4
|
-
import type { ConsumeMessage } from 'amqplib/properties';
|
|
5
3
|
import type { AsyncEventEmitter } from 'node-events-async';
|
|
4
|
+
import * as rabbit from 'rabbitmq-client';
|
|
6
5
|
import type { RabbitmqAdapter } from './rabbitmq-adapter';
|
|
7
6
|
/**
|
|
8
7
|
* RabbitmqContext class provides the context for handling RabbitMQ messages.
|
|
9
8
|
* It extends the ExecutionContext and implements the AsyncEventEmitter.
|
|
10
9
|
*/
|
|
11
10
|
export declare class RabbitmqContext extends ExecutionContext implements AsyncEventEmitter {
|
|
12
|
-
private _ackSent;
|
|
13
11
|
readonly protocol: OpraSchema.Transport;
|
|
14
12
|
readonly platform: string;
|
|
15
13
|
readonly adapter: RabbitmqAdapter;
|
|
@@ -18,24 +16,21 @@ export declare class RabbitmqContext extends ExecutionContext implements AsyncEv
|
|
|
18
16
|
readonly operation?: RpcOperation;
|
|
19
17
|
readonly operationHandler?: Function;
|
|
20
18
|
readonly queue: string;
|
|
21
|
-
readonly
|
|
22
|
-
readonly message:
|
|
19
|
+
readonly consumer: rabbit.Consumer;
|
|
20
|
+
readonly message: rabbit.AsyncMessage;
|
|
23
21
|
readonly content: any;
|
|
24
22
|
readonly headers: Record<string, any>;
|
|
23
|
+
readonly reply: RabbitmqAdapter.ReplyFunction;
|
|
25
24
|
/**
|
|
26
25
|
* Constructor
|
|
27
26
|
* @param init the context options
|
|
28
27
|
*/
|
|
29
28
|
constructor(init: RabbitmqContext.Initiator);
|
|
30
|
-
get properties(): import("amqplib/properties").MessageProperties;
|
|
31
|
-
get fields(): import("amqplib/properties").ConsumeMessageFields;
|
|
32
|
-
ack(): void;
|
|
33
|
-
nack(): void;
|
|
34
29
|
}
|
|
35
30
|
export declare namespace RabbitmqContext {
|
|
36
31
|
interface Initiator extends Omit<ExecutionContext.Initiator, 'document' | 'protocol' | 'documentNode'> {
|
|
37
32
|
adapter: RabbitmqAdapter;
|
|
38
|
-
|
|
33
|
+
consumer: rabbit.Consumer;
|
|
39
34
|
controller?: RpcController;
|
|
40
35
|
controllerInstance?: any;
|
|
41
36
|
operation?: RpcOperation;
|
|
@@ -43,6 +38,7 @@ export declare namespace RabbitmqContext {
|
|
|
43
38
|
content: any;
|
|
44
39
|
headers: Record<string, any>;
|
|
45
40
|
queue: string;
|
|
46
|
-
message:
|
|
41
|
+
message: rabbit.AsyncMessage;
|
|
42
|
+
reply: RabbitmqAdapter.ReplyFunction;
|
|
47
43
|
}
|
|
48
44
|
}
|