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