@opra/rabbitmq 1.16.0 → 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 -132
- package/cjs/rabbitmq-context.js +2 -20
- package/esm/config-builder.js +84 -0
- package/esm/rabbitmq-adapter.js +76 -133
- 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,99 +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
|
-
for (const
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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) {
|
|
104
|
+
this._emitError(e);
|
|
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
|
+
}));
|
|
152
118
|
}
|
|
153
119
|
}
|
|
120
|
+
await Promise.all(promises);
|
|
154
121
|
this._status = 'started';
|
|
155
122
|
}
|
|
156
|
-
catch (
|
|
123
|
+
catch (err) {
|
|
124
|
+
this._emitError(err);
|
|
157
125
|
await this.close();
|
|
158
|
-
throw e;
|
|
159
126
|
}
|
|
160
127
|
}
|
|
161
128
|
/**
|
|
162
129
|
* Closes all connections and stops the service
|
|
163
130
|
*/
|
|
164
131
|
async close() {
|
|
132
|
+
if (this._consumers.length)
|
|
133
|
+
await Promise.allSettled(this._consumers.map(consumer => consumer.close()));
|
|
165
134
|
await this._client?.close();
|
|
166
135
|
this._client = undefined;
|
|
136
|
+
this._consumers = [];
|
|
167
137
|
this._controllerInstances.clear();
|
|
168
138
|
this._status = 'idle';
|
|
169
139
|
}
|
|
@@ -171,43 +141,6 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
171
141
|
const controller = this.api.findController(controllerPath);
|
|
172
142
|
return controller && this._controllerInstances.get(controller);
|
|
173
143
|
}
|
|
174
|
-
/**
|
|
175
|
-
*
|
|
176
|
-
* @param controller
|
|
177
|
-
* @param instance
|
|
178
|
-
* @param operation
|
|
179
|
-
* @protected
|
|
180
|
-
*/
|
|
181
|
-
async _getOperationConfig(controller, instance, operation) {
|
|
182
|
-
if (typeof instance[operation.name] !== 'function')
|
|
183
|
-
return;
|
|
184
|
-
const proto = controller.ctor?.prototype || Object.getPrototypeOf(instance);
|
|
185
|
-
if (Reflect.hasMetadata(common_1.RPC_CONTROLLER_METADATA, proto, operation.name))
|
|
186
|
-
return;
|
|
187
|
-
const operationConfig = {
|
|
188
|
-
consumer: {
|
|
189
|
-
noAck: true,
|
|
190
|
-
},
|
|
191
|
-
};
|
|
192
|
-
if (this._config.defaults) {
|
|
193
|
-
if (this._config.defaults.consumer) {
|
|
194
|
-
Object.assign(operationConfig.consumer, this._config.defaults.consumer);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
let metadata = Reflect.getMetadata(constants_js_1.RMQ_OPERATION_METADATA, proto, operation.name);
|
|
198
|
-
if (!metadata) {
|
|
199
|
-
const configResolver = Reflect.getMetadata(constants_js_1.RMQ_OPERATION_METADATA_RESOLVER, proto, operation.name);
|
|
200
|
-
if (configResolver) {
|
|
201
|
-
metadata = await configResolver();
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (metadata) {
|
|
205
|
-
if (typeof metadata.consumer === 'object') {
|
|
206
|
-
Object.assign(operationConfig.consumer, metadata.consumer);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return operationConfig;
|
|
210
|
-
}
|
|
211
144
|
/**
|
|
212
145
|
*
|
|
213
146
|
* @param args
|
|
@@ -231,10 +164,17 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
231
164
|
this[core_1.kAssetCache].set(header, 'decode', decode);
|
|
232
165
|
}
|
|
233
166
|
});
|
|
234
|
-
|
|
167
|
+
return async (consumer, queue, message, _reply) => {
|
|
235
168
|
if (!message)
|
|
236
169
|
return;
|
|
237
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
|
+
};
|
|
238
178
|
const headers = {};
|
|
239
179
|
/** Create context */
|
|
240
180
|
const context = new rabbitmq_context_js_1.RabbitmqContext({
|
|
@@ -245,10 +185,11 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
245
185
|
operation,
|
|
246
186
|
operationHandler,
|
|
247
187
|
queue,
|
|
248
|
-
|
|
188
|
+
consumer,
|
|
249
189
|
message,
|
|
250
190
|
content: undefined,
|
|
251
191
|
headers,
|
|
192
|
+
reply,
|
|
252
193
|
});
|
|
253
194
|
try {
|
|
254
195
|
/** Parse and decode `payload` */
|
|
@@ -260,8 +201,8 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
260
201
|
}
|
|
261
202
|
// message.properties.
|
|
262
203
|
/** Parse and decode `headers` */
|
|
263
|
-
if (message.
|
|
264
|
-
for (const [k, v] of Object.entries(message.
|
|
204
|
+
if (message.headers) {
|
|
205
|
+
for (const [k, v] of Object.entries(message.headers)) {
|
|
265
206
|
const header = operation.findHeader(k);
|
|
266
207
|
const decode = this[core_1.kAssetCache].get(header, 'decode') || valgen_1.vg.isAny();
|
|
267
208
|
headers[k] = decode(Buffer.isBuffer(v) ? v.toString() : v);
|
|
@@ -270,7 +211,6 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
270
211
|
context.content = content;
|
|
271
212
|
}
|
|
272
213
|
catch (e) {
|
|
273
|
-
context.ack();
|
|
274
214
|
this._emitError(e, context);
|
|
275
215
|
return;
|
|
276
216
|
}
|
|
@@ -278,6 +218,8 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
278
218
|
try {
|
|
279
219
|
/** Call operation handler */
|
|
280
220
|
const result = await operationHandler.call(instance, context);
|
|
221
|
+
if (result !== undefined)
|
|
222
|
+
await reply(result);
|
|
281
223
|
await this.emitAsync('finish', context, result).catch(noOp);
|
|
282
224
|
}
|
|
283
225
|
catch (e) {
|
|
@@ -286,11 +228,13 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
286
228
|
};
|
|
287
229
|
}
|
|
288
230
|
async _parseContent(msg) {
|
|
289
|
-
if (!msg.
|
|
231
|
+
if (!Buffer.isBuffer(msg.body))
|
|
232
|
+
return msg.body;
|
|
233
|
+
if (!msg.body?.length)
|
|
290
234
|
return;
|
|
291
|
-
let content = msg.
|
|
292
|
-
if (msg.
|
|
293
|
-
switch (msg.
|
|
235
|
+
let content = msg.body;
|
|
236
|
+
if (msg.contentEncoding) {
|
|
237
|
+
switch (msg.contentEncoding) {
|
|
294
238
|
case 'gzip':
|
|
295
239
|
case 'x-gzip': {
|
|
296
240
|
content = await gunzipAsync(content);
|
|
@@ -316,12 +260,11 @@ class RabbitmqAdapter extends core_1.PlatformAdapter {
|
|
|
316
260
|
}
|
|
317
261
|
}
|
|
318
262
|
}
|
|
319
|
-
const mediaType = msg.
|
|
320
|
-
(0, content_type_1.parse)(msg.
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
charset = 'utf-8';
|
|
324
|
-
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';
|
|
325
268
|
content = iconv_lite_1.default.decode(content, charset);
|
|
326
269
|
if (type_is_1.default.is(mediaType.type, ['json']))
|
|
327
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,99 +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
|
-
for (const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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) {
|
|
100
|
+
this._emitError(e);
|
|
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
|
+
}));
|
|
148
114
|
}
|
|
149
115
|
}
|
|
116
|
+
await Promise.all(promises);
|
|
150
117
|
this._status = 'started';
|
|
151
118
|
}
|
|
152
|
-
catch (
|
|
119
|
+
catch (err) {
|
|
120
|
+
this._emitError(err);
|
|
153
121
|
await this.close();
|
|
154
|
-
throw e;
|
|
155
122
|
}
|
|
156
123
|
}
|
|
157
124
|
/**
|
|
158
125
|
* Closes all connections and stops the service
|
|
159
126
|
*/
|
|
160
127
|
async close() {
|
|
128
|
+
if (this._consumers.length)
|
|
129
|
+
await Promise.allSettled(this._consumers.map(consumer => consumer.close()));
|
|
161
130
|
await this._client?.close();
|
|
162
131
|
this._client = undefined;
|
|
132
|
+
this._consumers = [];
|
|
163
133
|
this._controllerInstances.clear();
|
|
164
134
|
this._status = 'idle';
|
|
165
135
|
}
|
|
@@ -167,43 +137,6 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
167
137
|
const controller = this.api.findController(controllerPath);
|
|
168
138
|
return controller && this._controllerInstances.get(controller);
|
|
169
139
|
}
|
|
170
|
-
/**
|
|
171
|
-
*
|
|
172
|
-
* @param controller
|
|
173
|
-
* @param instance
|
|
174
|
-
* @param operation
|
|
175
|
-
* @protected
|
|
176
|
-
*/
|
|
177
|
-
async _getOperationConfig(controller, instance, operation) {
|
|
178
|
-
if (typeof instance[operation.name] !== 'function')
|
|
179
|
-
return;
|
|
180
|
-
const proto = controller.ctor?.prototype || Object.getPrototypeOf(instance);
|
|
181
|
-
if (Reflect.hasMetadata(RPC_CONTROLLER_METADATA, proto, operation.name))
|
|
182
|
-
return;
|
|
183
|
-
const operationConfig = {
|
|
184
|
-
consumer: {
|
|
185
|
-
noAck: true,
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
if (this._config.defaults) {
|
|
189
|
-
if (this._config.defaults.consumer) {
|
|
190
|
-
Object.assign(operationConfig.consumer, this._config.defaults.consumer);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
let metadata = Reflect.getMetadata(RMQ_OPERATION_METADATA, proto, operation.name);
|
|
194
|
-
if (!metadata) {
|
|
195
|
-
const configResolver = Reflect.getMetadata(RMQ_OPERATION_METADATA_RESOLVER, proto, operation.name);
|
|
196
|
-
if (configResolver) {
|
|
197
|
-
metadata = await configResolver();
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (metadata) {
|
|
201
|
-
if (typeof metadata.consumer === 'object') {
|
|
202
|
-
Object.assign(operationConfig.consumer, metadata.consumer);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return operationConfig;
|
|
206
|
-
}
|
|
207
140
|
/**
|
|
208
141
|
*
|
|
209
142
|
* @param args
|
|
@@ -227,10 +160,17 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
227
160
|
this[kAssetCache].set(header, 'decode', decode);
|
|
228
161
|
}
|
|
229
162
|
});
|
|
230
|
-
|
|
163
|
+
return async (consumer, queue, message, _reply) => {
|
|
231
164
|
if (!message)
|
|
232
165
|
return;
|
|
233
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
|
+
};
|
|
234
174
|
const headers = {};
|
|
235
175
|
/** Create context */
|
|
236
176
|
const context = new RabbitmqContext({
|
|
@@ -241,10 +181,11 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
241
181
|
operation,
|
|
242
182
|
operationHandler,
|
|
243
183
|
queue,
|
|
244
|
-
|
|
184
|
+
consumer,
|
|
245
185
|
message,
|
|
246
186
|
content: undefined,
|
|
247
187
|
headers,
|
|
188
|
+
reply,
|
|
248
189
|
});
|
|
249
190
|
try {
|
|
250
191
|
/** Parse and decode `payload` */
|
|
@@ -256,8 +197,8 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
256
197
|
}
|
|
257
198
|
// message.properties.
|
|
258
199
|
/** Parse and decode `headers` */
|
|
259
|
-
if (message.
|
|
260
|
-
for (const [k, v] of Object.entries(message.
|
|
200
|
+
if (message.headers) {
|
|
201
|
+
for (const [k, v] of Object.entries(message.headers)) {
|
|
261
202
|
const header = operation.findHeader(k);
|
|
262
203
|
const decode = this[kAssetCache].get(header, 'decode') || vg.isAny();
|
|
263
204
|
headers[k] = decode(Buffer.isBuffer(v) ? v.toString() : v);
|
|
@@ -266,7 +207,6 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
266
207
|
context.content = content;
|
|
267
208
|
}
|
|
268
209
|
catch (e) {
|
|
269
|
-
context.ack();
|
|
270
210
|
this._emitError(e, context);
|
|
271
211
|
return;
|
|
272
212
|
}
|
|
@@ -274,6 +214,8 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
274
214
|
try {
|
|
275
215
|
/** Call operation handler */
|
|
276
216
|
const result = await operationHandler.call(instance, context);
|
|
217
|
+
if (result !== undefined)
|
|
218
|
+
await reply(result);
|
|
277
219
|
await this.emitAsync('finish', context, result).catch(noOp);
|
|
278
220
|
}
|
|
279
221
|
catch (e) {
|
|
@@ -282,11 +224,13 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
282
224
|
};
|
|
283
225
|
}
|
|
284
226
|
async _parseContent(msg) {
|
|
285
|
-
if (!msg.
|
|
227
|
+
if (!Buffer.isBuffer(msg.body))
|
|
228
|
+
return msg.body;
|
|
229
|
+
if (!msg.body?.length)
|
|
286
230
|
return;
|
|
287
|
-
let content = msg.
|
|
288
|
-
if (msg.
|
|
289
|
-
switch (msg.
|
|
231
|
+
let content = msg.body;
|
|
232
|
+
if (msg.contentEncoding) {
|
|
233
|
+
switch (msg.contentEncoding) {
|
|
290
234
|
case 'gzip':
|
|
291
235
|
case 'x-gzip': {
|
|
292
236
|
content = await gunzipAsync(content);
|
|
@@ -312,12 +256,11 @@ export class RabbitmqAdapter extends PlatformAdapter {
|
|
|
312
256
|
}
|
|
313
257
|
}
|
|
314
258
|
}
|
|
315
|
-
const mediaType = msg.
|
|
316
|
-
parseContentType(msg.
|
|
317
|
-
|
|
318
|
-
if (
|
|
319
|
-
charset = 'utf-8';
|
|
320
|
-
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';
|
|
321
264
|
content = iconv.decode(content, charset);
|
|
322
265
|
if (typeIs.is(mediaType.type, ['json']))
|
|
323
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
|
}
|