@opra/rabbitmq 1.16.1 → 1.17.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.
- package/cjs/config-builder.js +88 -0
- package/cjs/rabbitmq-adapter.js +75 -131
- package/cjs/rabbitmq-context.js +2 -20
- package/esm/config-builder.js +84 -0
- package/esm/rabbitmq-adapter.js +76 -132
- 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 +16 -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 => {
|
|
@@ -71,98 +71,69 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
71
71
|
if (this.status !== 'idle')
|
|
72
72
|
return;
|
|
73
73
|
this._status = 'starting';
|
|
74
|
-
const handlerArgs = [];
|
|
74
|
+
// const handlerArgs: HandlerArguments[] = [];
|
|
75
|
+
const configBuilder = new config_builder_js_1.ConfigBuilder(this.document, this._config);
|
|
76
|
+
await configBuilder.build();
|
|
77
|
+
this._client = new rabbit.Connection(configBuilder.connectionOptions);
|
|
75
78
|
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;
|
|
79
|
+
/** Establish connection */
|
|
80
|
+
await this._client.onConnect().catch(e => {
|
|
81
|
+
e.message = `RabbitMQ connection error. ${e.message}`;
|
|
120
82
|
throw e;
|
|
121
83
|
});
|
|
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 => {
|
|
84
|
+
this.logger?.info?.(`Connected RabbitMQ at ${configBuilder.connectionOptions.urls}`);
|
|
85
|
+
/** Subscribe to queues */
|
|
86
|
+
const promises = [];
|
|
87
|
+
for (const args of configBuilder.handlerArgs) {
|
|
88
|
+
for (const queue of args.topics) {
|
|
89
|
+
const handler = this._createHandler(args);
|
|
90
|
+
promises.push(new Promise((resolve, reject) => {
|
|
91
|
+
const consumer = this._client.createConsumer({
|
|
92
|
+
...args.consumerConfig,
|
|
93
|
+
queueOptions: {
|
|
94
|
+
...args.consumerConfig.queueOptions,
|
|
95
|
+
durable: args.consumerConfig.queueOptions?.durable ?? true,
|
|
96
|
+
},
|
|
97
|
+
queue,
|
|
98
|
+
}, async (msg, reply) => {
|
|
99
|
+
await this.emitAsync('message', msg, queue).catch(noOp);
|
|
100
|
+
try {
|
|
101
|
+
await handler(consumer, queue, msg, reply);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
145
104
|
this._emitError(e);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
this._consumers.push(consumer);
|
|
108
|
+
consumer.on('ready', () => {
|
|
109
|
+
this.logger?.info?.(`Subscribed to topic "${queue}"`);
|
|
110
|
+
resolve();
|
|
111
|
+
});
|
|
112
|
+
consumer.on('error', (err) => {
|
|
113
|
+
err.message = `Consumer error (${queue})". ${err.message}`;
|
|
114
|
+
err.queue = queue;
|
|
115
|
+
reject(err);
|
|
116
|
+
});
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
152
119
|
}
|
|
120
|
+
await Promise.all(promises);
|
|
153
121
|
this._status = 'started';
|
|
154
122
|
}
|
|
155
|
-
catch (
|
|
123
|
+
catch (err) {
|
|
124
|
+
this._emitError(err);
|
|
156
125
|
await this.close();
|
|
157
|
-
throw e;
|
|
158
126
|
}
|
|
159
127
|
}
|
|
160
128
|
/**
|
|
161
129
|
* Closes all connections and stops the service
|
|
162
130
|
*/
|
|
163
131
|
async close() {
|
|
132
|
+
if (this._consumers.length)
|
|
133
|
+
await Promise.allSettled(this._consumers.map(consumer => consumer.close()));
|
|
164
134
|
await this._client?.close();
|
|
165
135
|
this._client = undefined;
|
|
136
|
+
this._consumers = [];
|
|
166
137
|
this._controllerInstances.clear();
|
|
167
138
|
this._status = 'idle';
|
|
168
139
|
}
|
|
@@ -170,43 +141,6 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
170
141
|
const controller = this.api.findController(controllerPath);
|
|
171
142
|
return controller && this._controllerInstances.get(controller);
|
|
172
143
|
}
|
|
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
144
|
/**
|
|
211
145
|
*
|
|
212
146
|
* @param args
|
|
@@ -230,10 +164,17 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
230
164
|
this[core_1.kAssetCache].set(header, 'decode', decode);
|
|
231
165
|
}
|
|
232
166
|
});
|
|
233
|
-
|
|
167
|
+
return async (consumer, queue, message, _reply) => {
|
|
234
168
|
if (!message)
|
|
235
169
|
return;
|
|
236
170
|
const operationHandler = instance[operation.name];
|
|
171
|
+
let replyCalled = false;
|
|
172
|
+
const reply = async (body, envelope) => {
|
|
173
|
+
if (replyCalled)
|
|
174
|
+
return;
|
|
175
|
+
replyCalled = true;
|
|
176
|
+
return _reply(body, envelope);
|
|
177
|
+
};
|
|
237
178
|
const headers = {};
|
|
238
179
|
/** Create context */
|
|
239
180
|
const context = new rabbitmq_context_js_1.RabbitmqContext({
|
|
@@ -244,10 +185,11 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
244
185
|
operation,
|
|
245
186
|
operationHandler,
|
|
246
187
|
queue,
|
|
247
|
-
|
|
188
|
+
consumer,
|
|
248
189
|
message,
|
|
249
190
|
content: undefined,
|
|
250
191
|
headers,
|
|
192
|
+
reply,
|
|
251
193
|
});
|
|
252
194
|
try {
|
|
253
195
|
/** Parse and decode `payload` */
|
|
@@ -259,8 +201,8 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
259
201
|
}
|
|
260
202
|
// message.properties.
|
|
261
203
|
/** Parse and decode `headers` */
|
|
262
|
-
if (message.
|
|
263
|
-
for (const [k, v] of Object.entries(message.
|
|
204
|
+
if (message.headers) {
|
|
205
|
+
for (const [k, v] of Object.entries(message.headers)) {
|
|
264
206
|
const header = operation.findHeader(k);
|
|
265
207
|
const decode = this[core_1.kAssetCache].get(header, 'decode') || valgen_1.vg.isAny();
|
|
266
208
|
headers[k] = decode(Buffer.isBuffer(v) ? v.toString() : v);
|
|
@@ -269,7 +211,6 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
269
211
|
context.content = content;
|
|
270
212
|
}
|
|
271
213
|
catch (e) {
|
|
272
|
-
context.ack();
|
|
273
214
|
this._emitError(e, context);
|
|
274
215
|
return;
|
|
275
216
|
}
|
|
@@ -277,6 +218,8 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
277
218
|
try {
|
|
278
219
|
/** Call operation handler */
|
|
279
220
|
const result = await operationHandler.call(instance, context);
|
|
221
|
+
if (result !== undefined)
|
|
222
|
+
await reply(result);
|
|
280
223
|
await this.emitAsync('finish', context, result).catch(noOp);
|
|
281
224
|
}
|
|
282
225
|
catch (e) {
|
|
@@ -285,11 +228,13 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
285
228
|
};
|
|
286
229
|
}
|
|
287
230
|
async _parseContent(msg) {
|
|
288
|
-
if (!msg.
|
|
231
|
+
if (!Buffer.isBuffer(msg.body))
|
|
232
|
+
return msg.body;
|
|
233
|
+
if (!msg.body?.length)
|
|
289
234
|
return;
|
|
290
|
-
let content = msg.
|
|
291
|
-
if (msg.
|
|
292
|
-
switch (msg.
|
|
235
|
+
let content = msg.body;
|
|
236
|
+
if (msg.contentEncoding) {
|
|
237
|
+
switch (msg.contentEncoding) {
|
|
293
238
|
case 'gzip':
|
|
294
239
|
case 'x-gzip': {
|
|
295
240
|
content = await gunzipAsync(content);
|
|
@@ -315,12 +260,11 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
315
260
|
}
|
|
316
261
|
}
|
|
317
262
|
}
|
|
318
|
-
const mediaType = msg.
|
|
319
|
-
(0, content_type_1.parse)(msg.
|
|
320
|
-
|
|
321
|
-
if (
|
|
322
|
-
charset = 'utf-8';
|
|
323
|
-
if (charset) {
|
|
263
|
+
const mediaType = msg.contentType
|
|
264
|
+
? (0, content_type_1.parse)(msg.contentType || '')
|
|
265
|
+
: undefined;
|
|
266
|
+
if (mediaType && type_is_1.default.is(mediaType.type, ['json', 'xml', 'txt'])) {
|
|
267
|
+
const charset = (mediaType.parameters.charset || '').toLowerCase() || 'utf-8';
|
|
324
268
|
content = iconv_lite_1.default.decode(content, charset);
|
|
325
269
|
if (type_is_1.default.is(mediaType.type, ['json']))
|
|
326
270
|
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 => {
|
|
@@ -67,98 +67,69 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
67
67
|
if (this.status !== 'idle')
|
|
68
68
|
return;
|
|
69
69
|
this._status = 'starting';
|
|
70
|
-
const handlerArgs = [];
|
|
70
|
+
// const handlerArgs: HandlerArguments[] = [];
|
|
71
|
+
const configBuilder = new ConfigBuilder(this.document, this._config);
|
|
72
|
+
await configBuilder.build();
|
|
73
|
+
this._client = new rabbit.Connection(configBuilder.connectionOptions);
|
|
71
74
|
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;
|
|
75
|
+
/** Establish connection */
|
|
76
|
+
await this._client.onConnect().catch(e => {
|
|
77
|
+
e.message = `RabbitMQ connection error. ${e.message}`;
|
|
116
78
|
throw e;
|
|
117
79
|
});
|
|
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 => {
|
|
80
|
+
this.logger?.info?.(`Connected RabbitMQ at ${configBuilder.connectionOptions.urls}`);
|
|
81
|
+
/** Subscribe to queues */
|
|
82
|
+
const promises = [];
|
|
83
|
+
for (const args of configBuilder.handlerArgs) {
|
|
84
|
+
for (const queue of args.topics) {
|
|
85
|
+
const handler = this._createHandler(args);
|
|
86
|
+
promises.push(new Promise((resolve, reject) => {
|
|
87
|
+
const consumer = this._client.createConsumer({
|
|
88
|
+
...args.consumerConfig,
|
|
89
|
+
queueOptions: {
|
|
90
|
+
...args.consumerConfig.queueOptions,
|
|
91
|
+
durable: args.consumerConfig.queueOptions?.durable ?? true,
|
|
92
|
+
},
|
|
93
|
+
queue,
|
|
94
|
+
}, async (msg, reply) => {
|
|
95
|
+
await this.emitAsync('message', msg, queue).catch(noOp);
|
|
96
|
+
try {
|
|
97
|
+
await handler(consumer, queue, msg, reply);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
141
100
|
this._emitError(e);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
this._consumers.push(consumer);
|
|
104
|
+
consumer.on('ready', () => {
|
|
105
|
+
this.logger?.info?.(`Subscribed to topic "${queue}"`);
|
|
106
|
+
resolve();
|
|
107
|
+
});
|
|
108
|
+
consumer.on('error', (err) => {
|
|
109
|
+
err.message = `Consumer error (${queue})". ${err.message}`;
|
|
110
|
+
err.queue = queue;
|
|
111
|
+
reject(err);
|
|
112
|
+
});
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
148
115
|
}
|
|
116
|
+
await Promise.all(promises);
|
|
149
117
|
this._status = 'started';
|
|
150
118
|
}
|
|
151
|
-
catch (
|
|
119
|
+
catch (err) {
|
|
120
|
+
this._emitError(err);
|
|
152
121
|
await this.close();
|
|
153
|
-
throw e;
|
|
154
122
|
}
|
|
155
123
|
}
|
|
156
124
|
/**
|
|
157
125
|
* Closes all connections and stops the service
|
|
158
126
|
*/
|
|
159
127
|
async close() {
|
|
128
|
+
if (this._consumers.length)
|
|
129
|
+
await Promise.allSettled(this._consumers.map(consumer => consumer.close()));
|
|
160
130
|
await this._client?.close();
|
|
161
131
|
this._client = undefined;
|
|
132
|
+
this._consumers = [];
|
|
162
133
|
this._controllerInstances.clear();
|
|
163
134
|
this._status = 'idle';
|
|
164
135
|
}
|
|
@@ -166,43 +137,6 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
166
137
|
const controller = this.api.findController(controllerPath);
|
|
167
138
|
return controller && this._controllerInstances.get(controller);
|
|
168
139
|
}
|
|
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
140
|
/**
|
|
207
141
|
*
|
|
208
142
|
* @param args
|
|
@@ -226,10 +160,17 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
226
160
|
this[kAssetCache].set(header, 'decode', decode);
|
|
227
161
|
}
|
|
228
162
|
});
|
|
229
|
-
|
|
163
|
+
return async (consumer, queue, message, _reply) => {
|
|
230
164
|
if (!message)
|
|
231
165
|
return;
|
|
232
166
|
const operationHandler = instance[operation.name];
|
|
167
|
+
let replyCalled = false;
|
|
168
|
+
const reply = async (body, envelope) => {
|
|
169
|
+
if (replyCalled)
|
|
170
|
+
return;
|
|
171
|
+
replyCalled = true;
|
|
172
|
+
return _reply(body, envelope);
|
|
173
|
+
};
|
|
233
174
|
const headers = {};
|
|
234
175
|
/** Create context */
|
|
235
176
|
const context = new RabbitmqContext({
|
|
@@ -240,10 +181,11 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
240
181
|
operation,
|
|
241
182
|
operationHandler,
|
|
242
183
|
queue,
|
|
243
|
-
|
|
184
|
+
consumer,
|
|
244
185
|
message,
|
|
245
186
|
content: undefined,
|
|
246
187
|
headers,
|
|
188
|
+
reply,
|
|
247
189
|
});
|
|
248
190
|
try {
|
|
249
191
|
/** Parse and decode `payload` */
|
|
@@ -255,8 +197,8 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
255
197
|
}
|
|
256
198
|
// message.properties.
|
|
257
199
|
/** Parse and decode `headers` */
|
|
258
|
-
if (message.
|
|
259
|
-
for (const [k, v] of Object.entries(message.
|
|
200
|
+
if (message.headers) {
|
|
201
|
+
for (const [k, v] of Object.entries(message.headers)) {
|
|
260
202
|
const header = operation.findHeader(k);
|
|
261
203
|
const decode = this[kAssetCache].get(header, 'decode') || vg.isAny();
|
|
262
204
|
headers[k] = decode(Buffer.isBuffer(v) ? v.toString() : v);
|
|
@@ -265,7 +207,6 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
265
207
|
context.content = content;
|
|
266
208
|
}
|
|
267
209
|
catch (e) {
|
|
268
|
-
context.ack();
|
|
269
210
|
this._emitError(e, context);
|
|
270
211
|
return;
|
|
271
212
|
}
|
|
@@ -273,6 +214,8 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
273
214
|
try {
|
|
274
215
|
/** Call operation handler */
|
|
275
216
|
const result = await operationHandler.call(instance, context);
|
|
217
|
+
if (result !== undefined)
|
|
218
|
+
await reply(result);
|
|
276
219
|
await this.emitAsync('finish', context, result).catch(noOp);
|
|
277
220
|
}
|
|
278
221
|
catch (e) {
|
|
@@ -281,11 +224,13 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
281
224
|
};
|
|
282
225
|
}
|
|
283
226
|
async _parseContent(msg) {
|
|
284
|
-
if (!msg.
|
|
227
|
+
if (!Buffer.isBuffer(msg.body))
|
|
228
|
+
return msg.body;
|
|
229
|
+
if (!msg.body?.length)
|
|
285
230
|
return;
|
|
286
|
-
let content = msg.
|
|
287
|
-
if (msg.
|
|
288
|
-
switch (msg.
|
|
231
|
+
let content = msg.body;
|
|
232
|
+
if (msg.contentEncoding) {
|
|
233
|
+
switch (msg.contentEncoding) {
|
|
289
234
|
case 'gzip':
|
|
290
235
|
case 'x-gzip': {
|
|
291
236
|
content = await gunzipAsync(content);
|
|
@@ -311,12 +256,11 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
311
256
|
}
|
|
312
257
|
}
|
|
313
258
|
}
|
|
314
|
-
const mediaType = msg.
|
|
315
|
-
parseContentType(msg.
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
charset = 'utf-8';
|
|
319
|
-
if (charset) {
|
|
259
|
+
const mediaType = msg.contentType
|
|
260
|
+
? parseContentType(msg.contentType || '')
|
|
261
|
+
: undefined;
|
|
262
|
+
if (mediaType && typeIs.is(mediaType.type, ['json', 'xml', 'txt'])) {
|
|
263
|
+
const charset = (mediaType.parameters.charset || '').toLowerCase() || 'utf-8';
|
|
320
264
|
content = iconv.decode(content, charset);
|
|
321
265
|
if (typeIs.is(mediaType.type, ['json']))
|
|
322
266
|
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.0",
|
|
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.0",
|
|
18
|
+
"@opra/core": "^1.17.0",
|
|
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 _client?:
|
|
15
|
+
protected _client?: rabbit.Connection;
|
|
16
|
+
protected _consumers: rabbit.Consumer[];
|
|
28
17
|
protected _status: RabbitmqAdapter.Status;
|
|
29
18
|
readonly protocol: OpraSchema.Transport;
|
|
30
19
|
readonly platform = "rabbitmq";
|
|
@@ -48,21 +37,13 @@ export declare class RabbitmqAdapter extends PlatformAdapter<RabbitmqAdapter.Eve
|
|
|
48
37
|
*/
|
|
49
38
|
close(): Promise<void>;
|
|
50
39
|
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
40
|
/**
|
|
60
41
|
*
|
|
61
42
|
* @param args
|
|
62
43
|
* @protected
|
|
63
44
|
*/
|
|
64
|
-
protected _createHandler(args:
|
|
65
|
-
protected _parseContent(msg:
|
|
45
|
+
protected _createHandler(args: ConfigBuilder.OperationArguments): (consumer: rabbit.Consumer, queue: string, message: rabbit.AsyncMessage, _reply: RabbitmqAdapter.ReplyFunction) => Promise<void>;
|
|
46
|
+
protected _parseContent(msg: rabbit.AsyncMessage): Promise<any>;
|
|
66
47
|
protected _emitError(error: any, context?: RabbitmqContext): void;
|
|
67
48
|
protected _wrapExceptions(exceptions: any[]): OpraException[];
|
|
68
49
|
}
|
|
@@ -71,26 +52,22 @@ export declare class RabbitmqAdapter extends PlatformAdapter<RabbitmqAdapter.Eve
|
|
|
71
52
|
*/
|
|
72
53
|
export declare namespace RabbitmqAdapter {
|
|
73
54
|
type NextCallback = () => Promise<any>;
|
|
55
|
+
type ReplyFunction = (body: MessageBody, envelope?: Envelope) => Promise<void>;
|
|
74
56
|
type Status = 'idle' | 'starting' | 'started';
|
|
75
|
-
interface ConnectionOptions extends
|
|
76
|
-
urls?:
|
|
57
|
+
interface ConnectionOptions extends Pick<rabbit.ConnectionOptions, 'acquireTimeout' | 'connectionName' | 'frameMax' | 'heartbeat' | 'maxChannels' | 'retryHigh' | 'retryLow' | 'tls' | 'socket'> {
|
|
58
|
+
urls?: string[];
|
|
59
|
+
}
|
|
60
|
+
interface ConsumerConfig extends Pick<rabbit.ConsumerProps, 'concurrency' | 'requeue' | 'qos' | 'queueOptions' | 'exchanges' | 'exchangeBindings' | 'exclusive'> {
|
|
77
61
|
}
|
|
78
62
|
interface Config extends PlatformAdapter.Options {
|
|
79
|
-
connection
|
|
80
|
-
queues?: Record<string, amqplib.Options.AssertQueue>;
|
|
63
|
+
connection?: string | string[] | ConnectionOptions;
|
|
81
64
|
defaults?: {
|
|
82
|
-
consumer?:
|
|
65
|
+
consumer?: ConsumerConfig;
|
|
83
66
|
};
|
|
84
67
|
scope?: string;
|
|
85
68
|
interceptors?: (InterceptorFunction | IRabbitmqInterceptor)[];
|
|
86
69
|
logExtra?: boolean;
|
|
87
70
|
}
|
|
88
|
-
interface OperationOptions {
|
|
89
|
-
/**
|
|
90
|
-
* ConsumerConfig
|
|
91
|
-
*/
|
|
92
|
-
consumer?: amqplib.Options.Consume;
|
|
93
|
-
}
|
|
94
71
|
/**
|
|
95
72
|
* @type InterceptorFunction
|
|
96
73
|
*/
|
|
@@ -105,7 +82,6 @@ export declare namespace RabbitmqAdapter {
|
|
|
105
82
|
error: [Error, RabbitmqContext | undefined];
|
|
106
83
|
execute: [RabbitmqContext];
|
|
107
84
|
finish: [RabbitmqContext, any];
|
|
108
|
-
message: [
|
|
85
|
+
message: [rabbit.AsyncMessage, string];
|
|
109
86
|
}
|
|
110
87
|
}
|
|
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
|
}
|