rocketmq-client-nodejs-bate 1.0.12

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.
Files changed (209) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +186 -0
  3. package/dist/client/BaseClient.d.ts +95 -0
  4. package/dist/client/BaseClient.js +324 -0
  5. package/dist/client/ClientId.d.ts +24 -0
  6. package/dist/client/ClientId.js +33 -0
  7. package/dist/client/Logger.d.ts +25 -0
  8. package/dist/client/Logger.js +33 -0
  9. package/dist/client/RpcClient.d.ts +48 -0
  10. package/dist/client/RpcClient.js +219 -0
  11. package/dist/client/RpcClientManager.d.ts +42 -0
  12. package/dist/client/RpcClientManager.js +144 -0
  13. package/dist/client/SessionCredentials.d.ts +21 -0
  14. package/dist/client/SessionCredentials.js +19 -0
  15. package/dist/client/Settings.d.ts +31 -0
  16. package/dist/client/Settings.js +40 -0
  17. package/dist/client/TelemetrySession.d.ts +27 -0
  18. package/dist/client/TelemetrySession.js +110 -0
  19. package/dist/client/UserAgent.d.ts +25 -0
  20. package/dist/client/UserAgent.js +47 -0
  21. package/dist/client/index.d.ts +26 -0
  22. package/dist/client/index.js +43 -0
  23. package/dist/consumer/Assignment.d.ts +23 -0
  24. package/dist/consumer/Assignment.js +40 -0
  25. package/dist/consumer/Assignments.d.ts +24 -0
  26. package/dist/consumer/Assignments.js +47 -0
  27. package/dist/consumer/ConsumeResult.d.ts +20 -0
  28. package/dist/consumer/ConsumeResult.js +25 -0
  29. package/dist/consumer/ConsumeService.d.ts +28 -0
  30. package/dist/consumer/ConsumeService.js +49 -0
  31. package/dist/consumer/ConsumeTask.d.ts +24 -0
  32. package/dist/consumer/ConsumeTask.js +40 -0
  33. package/dist/consumer/Consumer.d.ts +38 -0
  34. package/dist/consumer/Consumer.js +111 -0
  35. package/dist/consumer/FifoConsumeService.d.ts +25 -0
  36. package/dist/consumer/FifoConsumeService.js +123 -0
  37. package/dist/consumer/FilterExpression.d.ts +25 -0
  38. package/dist/consumer/FilterExpression.js +40 -0
  39. package/dist/consumer/MessageListener.d.ts +21 -0
  40. package/dist/consumer/MessageListener.js +19 -0
  41. package/dist/consumer/ProcessQueue.d.ts +39 -0
  42. package/dist/consumer/ProcessQueue.js +286 -0
  43. package/dist/consumer/PushConsumer.d.ts +68 -0
  44. package/dist/consumer/PushConsumer.js +377 -0
  45. package/dist/consumer/PushSubscriptionSettings.d.ts +31 -0
  46. package/dist/consumer/PushSubscriptionSettings.js +97 -0
  47. package/dist/consumer/SimpleConsumer.d.ts +50 -0
  48. package/dist/consumer/SimpleConsumer.js +122 -0
  49. package/dist/consumer/SimpleSubscriptionSettings.d.ts +28 -0
  50. package/dist/consumer/SimpleSubscriptionSettings.js +57 -0
  51. package/dist/consumer/StandardConsumeService.d.ts +24 -0
  52. package/dist/consumer/StandardConsumeService.js +42 -0
  53. package/dist/consumer/SubscriptionLoadBalancer.d.ts +23 -0
  54. package/dist/consumer/SubscriptionLoadBalancer.js +46 -0
  55. package/dist/consumer/index.d.ts +32 -0
  56. package/dist/consumer/index.js +49 -0
  57. package/dist/exception/BadRequestException.d.ts +20 -0
  58. package/dist/exception/BadRequestException.js +28 -0
  59. package/dist/exception/ClientException.d.ts +20 -0
  60. package/dist/exception/ClientException.js +31 -0
  61. package/dist/exception/ForbiddenException.d.ts +20 -0
  62. package/dist/exception/ForbiddenException.js +28 -0
  63. package/dist/exception/InternalErrorException.d.ts +20 -0
  64. package/dist/exception/InternalErrorException.js +28 -0
  65. package/dist/exception/NotFoundException.d.ts +20 -0
  66. package/dist/exception/NotFoundException.js +28 -0
  67. package/dist/exception/PayloadTooLargeException.d.ts +20 -0
  68. package/dist/exception/PayloadTooLargeException.js +28 -0
  69. package/dist/exception/PaymentRequiredException.d.ts +20 -0
  70. package/dist/exception/PaymentRequiredException.js +28 -0
  71. package/dist/exception/ProxyTimeoutException.d.ts +20 -0
  72. package/dist/exception/ProxyTimeoutException.js +28 -0
  73. package/dist/exception/RequestHeaderFieldsTooLargeException.d.ts +20 -0
  74. package/dist/exception/RequestHeaderFieldsTooLargeException.js +28 -0
  75. package/dist/exception/StatusChecker.d.ts +20 -0
  76. package/dist/exception/StatusChecker.js +98 -0
  77. package/dist/exception/TooManyRequestsException.d.ts +20 -0
  78. package/dist/exception/TooManyRequestsException.js +28 -0
  79. package/dist/exception/UnauthorizedException.d.ts +20 -0
  80. package/dist/exception/UnauthorizedException.js +28 -0
  81. package/dist/exception/UnsupportedException.d.ts +20 -0
  82. package/dist/exception/UnsupportedException.js +28 -0
  83. package/dist/exception/index.d.ts +29 -0
  84. package/dist/exception/index.js +46 -0
  85. package/dist/index.d.ts +22 -0
  86. package/dist/index.js +39 -0
  87. package/dist/message/Message.d.ts +38 -0
  88. package/dist/message/Message.js +57 -0
  89. package/dist/message/MessageId.d.ts +59 -0
  90. package/dist/message/MessageId.js +123 -0
  91. package/dist/message/MessageView.d.ts +38 -0
  92. package/dist/message/MessageView.js +90 -0
  93. package/dist/message/PublishingMessage.d.ts +30 -0
  94. package/dist/message/PublishingMessage.js +100 -0
  95. package/dist/message/index.d.ts +20 -0
  96. package/dist/message/index.js +37 -0
  97. package/dist/producer/Producer.d.ts +55 -0
  98. package/dist/producer/Producer.js +318 -0
  99. package/dist/producer/PublishingLoadBalancer.d.ts +24 -0
  100. package/dist/producer/PublishingLoadBalancer.js +82 -0
  101. package/dist/producer/PublishingSettings.d.ts +28 -0
  102. package/dist/producer/PublishingSettings.js +70 -0
  103. package/dist/producer/RecallReceipt.d.ts +25 -0
  104. package/dist/producer/RecallReceipt.js +34 -0
  105. package/dist/producer/SendReceipt.d.ts +29 -0
  106. package/dist/producer/SendReceipt.js +60 -0
  107. package/dist/producer/Transaction.d.ts +28 -0
  108. package/dist/producer/Transaction.js +70 -0
  109. package/dist/producer/TransactionChecker.d.ts +21 -0
  110. package/dist/producer/TransactionChecker.js +19 -0
  111. package/dist/producer/index.d.ts +22 -0
  112. package/dist/producer/index.js +40 -0
  113. package/dist/retry/ExponentialBackoffRetryPolicy.d.ts +27 -0
  114. package/dist/retry/ExponentialBackoffRetryPolicy.js +64 -0
  115. package/dist/retry/RetryPolicy.d.ts +46 -0
  116. package/dist/retry/RetryPolicy.js +19 -0
  117. package/dist/retry/index.d.ts +18 -0
  118. package/dist/retry/index.js +35 -0
  119. package/dist/route/Broker.d.ts +25 -0
  120. package/dist/route/Broker.js +40 -0
  121. package/dist/route/Endpoints.d.ts +30 -0
  122. package/dist/route/Endpoints.js +71 -0
  123. package/dist/route/MessageQueue.d.ts +27 -0
  124. package/dist/route/MessageQueue.js +47 -0
  125. package/dist/route/TopicRouteData.d.ts +24 -0
  126. package/dist/route/TopicRouteData.js +37 -0
  127. package/dist/route/index.d.ts +20 -0
  128. package/dist/route/index.js +37 -0
  129. package/dist/util/index.d.ts +35 -0
  130. package/dist/util/index.js +88 -0
  131. package/package.json +62 -0
  132. package/proto/apache/rocketmq/v2/admin.proto +43 -0
  133. package/proto/apache/rocketmq/v2/admin_grpc_pb.d.ts +41 -0
  134. package/proto/apache/rocketmq/v2/admin_grpc_pb.js +60 -0
  135. package/proto/apache/rocketmq/v2/admin_pb.d.ts +56 -0
  136. package/proto/apache/rocketmq/v2/admin_pb.js +340 -0
  137. package/proto/apache/rocketmq/v2/definition.proto +570 -0
  138. package/proto/apache/rocketmq/v2/definition_grpc_pb.js +1 -0
  139. package/proto/apache/rocketmq/v2/definition_pb.d.ts +885 -0
  140. package/proto/apache/rocketmq/v2/definition_pb.js +6141 -0
  141. package/proto/apache/rocketmq/v2/service.proto +443 -0
  142. package/proto/apache/rocketmq/v2/service_grpc_pb.d.ts +294 -0
  143. package/proto/apache/rocketmq/v2/service_grpc_pb.js +637 -0
  144. package/proto/apache/rocketmq/v2/service_pb.d.ts +1249 -0
  145. package/proto/apache/rocketmq/v2/service_pb.js +9723 -0
  146. package/src/client/BaseClient.ts +404 -0
  147. package/src/client/ClientId.ts +31 -0
  148. package/src/client/Logger.ts +36 -0
  149. package/src/client/RpcClient.ts +258 -0
  150. package/src/client/RpcClientManager.ts +180 -0
  151. package/src/client/SessionCredentials.ts +22 -0
  152. package/src/client/Settings.ts +46 -0
  153. package/src/client/TelemetrySession.ts +130 -0
  154. package/src/client/UserAgent.ts +45 -0
  155. package/src/client/index.ts +27 -0
  156. package/src/consumer/Assignment.ts +39 -0
  157. package/src/consumer/Assignments.ts +46 -0
  158. package/src/consumer/ConsumeResult.ts +21 -0
  159. package/src/consumer/ConsumeService.ts +54 -0
  160. package/src/consumer/ConsumeTask.ts +40 -0
  161. package/src/consumer/Consumer.ts +129 -0
  162. package/src/consumer/FifoConsumeService.ts +136 -0
  163. package/src/consumer/FilterExpression.ts +42 -0
  164. package/src/consumer/MessageListener.ts +23 -0
  165. package/src/consumer/ProcessQueue.ts +326 -0
  166. package/src/consumer/PushConsumer.ts +473 -0
  167. package/src/consumer/PushSubscriptionSettings.ts +123 -0
  168. package/src/consumer/SimpleConsumer.ts +153 -0
  169. package/src/consumer/SimpleSubscriptionSettings.ts +64 -0
  170. package/src/consumer/StandardConsumeService.ts +44 -0
  171. package/src/consumer/SubscriptionLoadBalancer.ts +47 -0
  172. package/src/consumer/index.ts +33 -0
  173. package/src/exception/BadRequestException.ts +25 -0
  174. package/src/exception/ClientException.ts +29 -0
  175. package/src/exception/ForbiddenException.ts +25 -0
  176. package/src/exception/InternalErrorException.ts +25 -0
  177. package/src/exception/NotFoundException.ts +25 -0
  178. package/src/exception/PayloadTooLargeException.ts +25 -0
  179. package/src/exception/PaymentRequiredException.ts +25 -0
  180. package/src/exception/ProxyTimeoutException.ts +25 -0
  181. package/src/exception/RequestHeaderFieldsTooLargeException.ts +25 -0
  182. package/src/exception/StatusChecker.ts +94 -0
  183. package/src/exception/TooManyRequestsException.ts +25 -0
  184. package/src/exception/UnauthorizedException.ts +25 -0
  185. package/src/exception/UnsupportedException.ts +25 -0
  186. package/src/exception/index.ts +30 -0
  187. package/src/index.ts +23 -0
  188. package/src/message/Message.ts +67 -0
  189. package/src/message/MessageId.ts +123 -0
  190. package/src/message/MessageView.ts +94 -0
  191. package/src/message/PublishingMessage.ts +104 -0
  192. package/src/message/index.ts +21 -0
  193. package/src/producer/Producer.ts +388 -0
  194. package/src/producer/PublishingLoadBalancer.ts +85 -0
  195. package/src/producer/PublishingSettings.ts +78 -0
  196. package/src/producer/RecallReceipt.ts +32 -0
  197. package/src/producer/SendReceipt.ts +63 -0
  198. package/src/producer/Transaction.ts +86 -0
  199. package/src/producer/TransactionChecker.ts +23 -0
  200. package/src/producer/index.ts +24 -0
  201. package/src/retry/ExponentialBackoffRetryPolicy.ts +76 -0
  202. package/src/retry/RetryPolicy.ts +51 -0
  203. package/src/retry/index.ts +19 -0
  204. package/src/route/Broker.ts +39 -0
  205. package/src/route/Endpoints.ts +70 -0
  206. package/src/route/MessageQueue.ts +49 -0
  207. package/src/route/TopicRouteData.ts +38 -0
  208. package/src/route/index.ts +21 -0
  209. package/src/util/index.ts +83 -0
@@ -0,0 +1,473 @@
1
+ /**
2
+ * Licensed to the Apache Software Foundation (ASF) under one or more
3
+ * contributor license agreements. See the NOTICE file distributed with
4
+ * this work for additional information regarding copyright ownership.
5
+ * The ASF licenses this file to You under the Apache License, Version 2.0
6
+ * (the "License"); you may not use this file except in compliance with
7
+ * the License. You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import { ClientType } from '../../proto/apache/rocketmq/v2/definition_pb';
19
+ import {
20
+ AckMessageRequest,
21
+ ChangeInvisibleDurationRequest,
22
+ ForwardMessageToDeadLetterQueueRequest,
23
+ HeartbeatRequest,
24
+ NotifyClientTerminationRequest,
25
+ QueryAssignmentRequest,
26
+ ReceiveMessageRequest,
27
+ } from '../../proto/apache/rocketmq/v2/service_pb';
28
+ import { MessageView } from '../message';
29
+ import { MessageQueue, TopicRouteData } from '../route';
30
+ import { StatusChecker } from '../exception';
31
+ import { RetryPolicy } from '../retry';
32
+ import { createDuration, createResource } from '../util';
33
+ import { Consumer, ConsumerOptions } from './Consumer';
34
+ import { FilterExpression } from './FilterExpression';
35
+ import { PushSubscriptionSettings } from './PushSubscriptionSettings';
36
+ import { ConsumeService } from './ConsumeService';
37
+ import { StandardConsumeService } from './StandardConsumeService';
38
+ import { FifoConsumeService } from './FifoConsumeService';
39
+ import { ProcessQueue } from './ProcessQueue';
40
+ import { Assignment } from './Assignment';
41
+ import { Assignments } from './Assignments';
42
+ import { MessageListener } from './MessageListener';
43
+
44
+ const ASSIGNMENT_SCAN_SCHEDULE_DELAY = 1000;
45
+ const ASSIGNMENT_SCAN_SCHEDULE_PERIOD = 5000;
46
+
47
+ export interface PushConsumerOptions extends ConsumerOptions {
48
+ subscriptions: Map<string/* topic */, FilterExpression | string>;
49
+ messageListener: MessageListener;
50
+ maxCacheMessageCount?: number;
51
+ maxCacheMessageSizeInBytes?: number;
52
+ longPollingTimeout?: number;
53
+ enableFifoConsumeAccelerator?: boolean;
54
+ }
55
+
56
+ class ConsumeMetrics {
57
+ receptionTimes = 0;
58
+ receivedMessagesQuantity = 0;
59
+ consumptionOkQuantity = 0;
60
+ consumptionErrorQuantity = 0;
61
+ }
62
+
63
+ export class PushConsumer extends Consumer {
64
+ readonly #pushSubscriptionSettings: PushSubscriptionSettings;
65
+ readonly #subscriptionExpressions = new Map<string, FilterExpression>();
66
+ readonly #cacheAssignments = new Map<string, Assignments>();
67
+ readonly #messageListener: MessageListener;
68
+ readonly #maxCacheMessageCount: number;
69
+ readonly #maxCacheMessageSizeInBytes: number;
70
+ readonly #enableFifoConsumeAccelerator: boolean;
71
+ readonly #processQueueTable = new Map<string /* mq key */, { mq: MessageQueue; pq: ProcessQueue }>();
72
+ readonly #metrics = new ConsumeMetrics();
73
+ #consumeService!: ConsumeService;
74
+ #scanAssignmentTimer?: NodeJS.Timeout;
75
+
76
+ constructor(options: PushConsumerOptions) {
77
+ options.topics = Array.from(options.subscriptions.keys());
78
+ super(options);
79
+
80
+ for (const [ topic, filter ] of options.subscriptions.entries()) {
81
+ if (typeof filter === 'string') {
82
+ this.#subscriptionExpressions.set(topic, new FilterExpression(filter));
83
+ } else {
84
+ this.#subscriptionExpressions.set(topic, filter);
85
+ }
86
+ }
87
+
88
+ this.#messageListener = options.messageListener;
89
+ this.#maxCacheMessageCount = options.maxCacheMessageCount ?? 1024;
90
+ this.#maxCacheMessageSizeInBytes = options.maxCacheMessageSizeInBytes ?? 64 * 1024 * 1024;
91
+ this.#enableFifoConsumeAccelerator = options.enableFifoConsumeAccelerator ?? false;
92
+
93
+ this.#pushSubscriptionSettings = new PushSubscriptionSettings(
94
+ options.namespace, this.clientId, this.endpoints,
95
+ this.consumerGroup, this.requestTimeout, this.#subscriptionExpressions,
96
+ );
97
+ }
98
+
99
+ async startup() {
100
+ this.logger.info('Begin to start the rocketmq push consumer, clientId=%s, consumerGroup=%s',
101
+ this.clientId, this.consumerGroup);
102
+ await super.startup();
103
+ this.logger.info('Super startup completed, clientId=%s', this.clientId);
104
+ try {
105
+ this.#consumeService = this.#createConsumeService();
106
+ // Start scanning assignments periodically
107
+ this.logger.info('Starting assignment scanning, clientId=%s', this.clientId);
108
+ setTimeout(() => this.#scanAssignments(), ASSIGNMENT_SCAN_SCHEDULE_DELAY);
109
+ this.#scanAssignmentTimer = setInterval(() => this.#scanAssignments(), ASSIGNMENT_SCAN_SCHEDULE_PERIOD);
110
+ this.logger.info('Push consumer started successfully, clientId=%s', this.clientId);
111
+ } catch (err) {
112
+ this.logger.error('Failed to start push consumer, cleaning up resources, clientId=%s, error=%s',
113
+ this.clientId, err);
114
+ // Clean up timers if initialization fails
115
+ if (this.#scanAssignmentTimer) {
116
+ clearInterval(this.#scanAssignmentTimer);
117
+ this.#scanAssignmentTimer = undefined;
118
+ }
119
+ throw err;
120
+ }
121
+ }
122
+
123
+ async shutdown() {
124
+ this.logger.info('Begin to shutdown the rocketmq push consumer, clientId=%s', this.clientId);
125
+ // Stop scanning assignments
126
+ if (this.#scanAssignmentTimer) {
127
+ clearInterval(this.#scanAssignmentTimer);
128
+ this.#scanAssignmentTimer = undefined;
129
+ }
130
+ // Drop all process queues
131
+ this.logger.info('Dropping all process queues, clientId=%s, queueCount=%d',
132
+ this.clientId, this.#processQueueTable.size);
133
+ for (const { pq } of this.#processQueueTable.values()) {
134
+ pq.drop();
135
+ pq.abort();
136
+ }
137
+ this.#processQueueTable.clear();
138
+ // Shutdown consume service
139
+ if (this.#consumeService) {
140
+ this.logger.info('Shutting down consume service, clientId=%s', this.clientId);
141
+ this.#consumeService.abort();
142
+ }
143
+ await super.shutdown();
144
+ this.logger.info('Push consumer has been shutdown successfully, clientId=%s', this.clientId);
145
+ }
146
+
147
+ protected getSettings() {
148
+ return this.#pushSubscriptionSettings;
149
+ }
150
+
151
+ protected wrapHeartbeatRequest() {
152
+ return new HeartbeatRequest()
153
+ .setClientType(ClientType.PUSH_CONSUMER)
154
+ .setGroup(createResource(this.consumerGroup));
155
+ }
156
+
157
+ protected wrapNotifyClientTerminationRequest() {
158
+ return new NotifyClientTerminationRequest()
159
+ .setGroup(createResource(this.consumerGroup));
160
+ }
161
+
162
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
163
+ protected onTopicRouteDataUpdate(_topic: string, _topicRouteData: TopicRouteData) {
164
+ // No-op for push consumer; assignments are queried separately
165
+ }
166
+
167
+ #createConsumeService(): ConsumeService {
168
+ if (this.#pushSubscriptionSettings.isFifo()) {
169
+ return new FifoConsumeService(this.clientId, this.#messageListener, this.#enableFifoConsumeAccelerator);
170
+ }
171
+ return new StandardConsumeService(this.clientId, this.#messageListener);
172
+ }
173
+
174
+ async subscribe(topic: string, filterExpression: FilterExpression) {
175
+ await this.getRouteData(topic);
176
+ this.#subscriptionExpressions.set(topic, filterExpression);
177
+ }
178
+
179
+ unsubscribe(topic: string) {
180
+ this.#subscriptionExpressions.delete(topic);
181
+ }
182
+
183
+ // --- Public methods for ProcessQueue access ---
184
+
185
+ get requestTimeoutValue(): number {
186
+ return this.requestTimeout;
187
+ }
188
+
189
+ getPushConsumerSettings(): PushSubscriptionSettings {
190
+ return this.#pushSubscriptionSettings;
191
+ }
192
+
193
+ getConsumerGroup(): string {
194
+ return this.consumerGroup;
195
+ }
196
+
197
+ getConsumeService(): ConsumeService {
198
+ return this.#consumeService;
199
+ }
200
+
201
+ getRetryPolicy(): RetryPolicy | undefined {
202
+ return this.#pushSubscriptionSettings.getRetryPolicy();
203
+ }
204
+
205
+ getClientId(): string {
206
+ return this.clientId;
207
+ }
208
+
209
+ // --- Metrics access ---
210
+
211
+ getReceptionTimes(): number {
212
+ return this.#metrics.receptionTimes;
213
+ }
214
+
215
+ getReceivedMessagesQuantity(): number {
216
+ return this.#metrics.receivedMessagesQuantity;
217
+ }
218
+
219
+ getConsumptionOkQuantity(): number {
220
+ return this.#metrics.consumptionOkQuantity;
221
+ }
222
+
223
+ getConsumptionErrorQuantity(): number {
224
+ return this.#metrics.consumptionErrorQuantity;
225
+ }
226
+
227
+ incrementReceptionTimes() {
228
+ this.#metrics.receptionTimes++;
229
+ }
230
+
231
+ incrementReceivedMessagesQuantity(count: number) {
232
+ this.#metrics.receivedMessagesQuantity += count;
233
+ }
234
+
235
+ incrementConsumptionOkQuantity() {
236
+ this.#metrics.consumptionOkQuantity++;
237
+ }
238
+
239
+ incrementConsumptionErrorQuantity() {
240
+ this.#metrics.consumptionErrorQuantity++;
241
+ }
242
+
243
+ // --- Statistics ---
244
+
245
+ doStats() {
246
+ const receptionTimes = this.#metrics.receptionTimes;
247
+ this.#metrics.receptionTimes = 0;
248
+ const receivedMessagesQuantity = this.#metrics.receivedMessagesQuantity;
249
+ this.#metrics.receivedMessagesQuantity = 0;
250
+ const consumptionOkQuantity = this.#metrics.consumptionOkQuantity;
251
+ this.#metrics.consumptionOkQuantity = 0;
252
+ const consumptionErrorQuantity = this.#metrics.consumptionErrorQuantity;
253
+ this.#metrics.consumptionErrorQuantity = 0;
254
+
255
+ this.logger.info('clientId=%s, consumerGroup=%s, receptionTimes=%d, receivedMessagesQuantity=%d, '
256
+ + 'consumptionOkQuantity=%d, consumptionErrorQuantity=%d',
257
+ this.clientId, this.consumerGroup, receptionTimes, receivedMessagesQuantity,
258
+ consumptionOkQuantity, consumptionErrorQuantity);
259
+ }
260
+
261
+ wrapPushReceiveMessageRequest(
262
+ batchSize: number,
263
+ mq: MessageQueue,
264
+ filterExpression: FilterExpression,
265
+ longPollingTimeout: number,
266
+ attemptId?: string,
267
+ ) {
268
+ const request = new ReceiveMessageRequest()
269
+ .setGroup(createResource(this.consumerGroup))
270
+ .setMessageQueue(mq.toProtobuf())
271
+ .setFilterExpression(filterExpression.toProtobuf())
272
+ .setLongPollingTimeout(createDuration(longPollingTimeout))
273
+ .setBatchSize(batchSize)
274
+ .setAutoRenew(true);
275
+ if (attemptId) {
276
+ request.setAttemptId(attemptId);
277
+ }
278
+ return request;
279
+ }
280
+
281
+ async receiveMessage(request: ReceiveMessageRequest, mq: MessageQueue, awaitDuration: number) {
282
+ return super.receiveMessage(request, mq, awaitDuration);
283
+ }
284
+
285
+ wrapAckMessageRequest(messageView: MessageView) {
286
+ const request = new AckMessageRequest()
287
+ .setGroup(createResource(this.consumerGroup))
288
+ .setTopic(createResource(messageView.topic));
289
+ request.addEntries()
290
+ .setMessageId(messageView.messageId)
291
+ .setReceiptHandle(messageView.receiptHandle);
292
+ return request;
293
+ }
294
+
295
+ wrapChangeInvisibleDurationRequest(messageView: MessageView, invisibleDuration: number) {
296
+ return new ChangeInvisibleDurationRequest()
297
+ .setGroup(createResource(this.consumerGroup))
298
+ .setTopic(createResource(messageView.topic))
299
+ .setReceiptHandle(messageView.receiptHandle)
300
+ .setInvisibleDuration(createDuration(invisibleDuration))
301
+ .setMessageId(messageView.messageId);
302
+ }
303
+
304
+ wrapForwardMessageToDeadLetterQueueRequest(messageView: MessageView) {
305
+ const retryPolicy = this.getRetryPolicy();
306
+ return new ForwardMessageToDeadLetterQueueRequest()
307
+ .setGroup(createResource(this.consumerGroup))
308
+ .setTopic(createResource(messageView.topic))
309
+ .setReceiptHandle(messageView.receiptHandle)
310
+ .setMessageId(messageView.messageId)
311
+ .setDeliveryAttempt(messageView.deliveryAttempt ?? 0)
312
+ .setMaxDeliveryAttempts(retryPolicy?.getMaxAttempts() ?? 1);
313
+ }
314
+
315
+ // --- Internal: queue size and cache threshold ---
316
+
317
+ getQueueSize(): number {
318
+ return this.#processQueueTable.size;
319
+ }
320
+
321
+ cacheMessageBytesThresholdPerQueue(): number {
322
+ const size = this.getQueueSize();
323
+ if (size <= 0) return 0;
324
+ return Math.max(1, Math.floor(this.#maxCacheMessageSizeInBytes / size));
325
+ }
326
+
327
+ cacheMessageCountThresholdPerQueue(): number {
328
+ const size = this.getQueueSize();
329
+ if (size <= 0) return 0;
330
+ return Math.max(1, Math.floor(this.#maxCacheMessageCount / size));
331
+ }
332
+
333
+ // --- Internal: assignment scanning ---
334
+
335
+ #scanAssignments() {
336
+ try {
337
+ this.logger.debug?.('Scanning assignments, clientId=%s, subscriptionCount=%d',
338
+ this.clientId, this.#subscriptionExpressions.size);
339
+ for (const [ topic, filterExpression ] of this.#subscriptionExpressions) {
340
+ const existed = this.#cacheAssignments.get(topic);
341
+ this.#queryAssignment(topic)
342
+ .then(latest => {
343
+ this.logger.debug?.('Query assignment result, topic=%s, clientId=%s, assignmentCount=%d',
344
+ topic, this.clientId, latest.getAssignmentList().length);
345
+ if (latest.getAssignmentList().length === 0) {
346
+ if (!existed || existed.getAssignmentList().length === 0) {
347
+ this.logger.info('Acquired empty assignments from remote, would scan later, topic=%s, '
348
+ + 'clientId=%s', topic, this.clientId);
349
+ return;
350
+ }
351
+ this.logger.warn('Attention!!! acquired empty assignments from remote, but existed assignments'
352
+ + ' is not empty, topic=%s, clientId=%s', topic, this.clientId);
353
+ }
354
+
355
+ if (!latest.equals(existed)) {
356
+ this.logger.info('Assignments of topic=%s has changed, %j => %j, clientId=%s', topic, existed,
357
+ latest, this.clientId);
358
+ this.#syncProcessQueue(topic, latest, filterExpression);
359
+ this.#cacheAssignments.set(topic, latest);
360
+ return;
361
+ }
362
+ this.logger.debug?.('Assignments of topic=%s remains the same, assignments=%j, clientId=%s', topic,
363
+ existed, this.clientId);
364
+ // Process queue may be dropped, need to be synchronized anyway.
365
+ this.#syncProcessQueue(topic, latest, filterExpression);
366
+ })
367
+ .catch(err => {
368
+ this.logger.error('Exception raised while scanning the assignments, topic=%s, clientId=%s, error=%s',
369
+ topic, this.clientId, err);
370
+ });
371
+ }
372
+ } catch (err) {
373
+ this.logger.error('Exception raised while scanning the assignments for all topics, clientId=%s, error=%s',
374
+ this.clientId, err);
375
+ }
376
+ }
377
+
378
+ async #queryAssignment(topic: string): Promise<Assignments> {
379
+ const topicRouteData = await this.getRouteData(topic);
380
+ const endpointsList = topicRouteData.getTotalEndpoints();
381
+ if (endpointsList.length === 0) {
382
+ throw new Error(`No endpoints available for topic=${topic}`);
383
+ }
384
+ const endpoints = endpointsList[0];
385
+
386
+ const request = new QueryAssignmentRequest()
387
+ .setTopic(createResource(topic))
388
+ .setGroup(createResource(this.consumerGroup))
389
+ .setEndpoints(this.endpoints.toProtobuf());
390
+
391
+ const response = await this.rpcClientManager.queryAssignment(
392
+ endpoints, request, this.requestTimeout,
393
+ );
394
+ const status = response.getStatus();
395
+ if (!status) {
396
+ throw new Error('Missing status in query assignment response');
397
+ }
398
+ StatusChecker.check(status.toObject());
399
+
400
+ const assignmentList = response.getAssignmentsList().map(assignment => {
401
+ const mqPb = assignment.getMessageQueue()!;
402
+ const mq = new MessageQueue(mqPb);
403
+ return new Assignment(mq);
404
+ });
405
+
406
+ return new Assignments(assignmentList);
407
+ }
408
+
409
+ #syncProcessQueue(topic: string, assignments: Assignments, filterExpression: FilterExpression) {
410
+ const latestMqKeys = new Set<string>();
411
+ for (const assignment of assignments.getAssignmentList()) {
412
+ latestMqKeys.add(this.#mqKey(assignment.messageQueue));
413
+ }
414
+
415
+ // Find active message queues
416
+ const activeMqKeys = new Set<string>();
417
+ for (const [ mqKey, { mq, pq }] of this.#processQueueTable) {
418
+ if (mq.topic.name !== topic) {
419
+ continue;
420
+ }
421
+ if (!latestMqKeys.has(mqKey)) {
422
+ this.logger.info('Drop message queue according to the latest assignmentList, mq=%s@%s@%s, clientId=%s',
423
+ mq.topic.name, mq.broker.name, mq.queueId, this.clientId);
424
+ this.#dropProcessQueue(mqKey);
425
+ continue;
426
+ }
427
+ if (pq.expired()) {
428
+ this.logger.warn('Drop message queue because it is expired, mq=%s@%s@%s, clientId=%s',
429
+ mq.topic.name, mq.broker.name, mq.queueId, this.clientId);
430
+ this.#dropProcessQueue(mqKey);
431
+ continue;
432
+ }
433
+ activeMqKeys.add(mqKey);
434
+ }
435
+
436
+ // Create new process queues for new assignments
437
+ for (const assignment of assignments.getAssignmentList()) {
438
+ const mqKey = this.#mqKey(assignment.messageQueue);
439
+ if (activeMqKeys.has(mqKey)) {
440
+ continue;
441
+ }
442
+ const processQueue = this.#createProcessQueue(assignment.messageQueue, filterExpression);
443
+ if (processQueue) {
444
+ this.logger.info('Start to fetch message from remote, mq=%s@%s@%s, clientId=%s',
445
+ assignment.messageQueue.topic.name, assignment.messageQueue.broker.name,
446
+ assignment.messageQueue.queueId, this.clientId);
447
+ processQueue.fetchMessageImmediately();
448
+ }
449
+ }
450
+ }
451
+
452
+ #mqKey(mq: MessageQueue): string {
453
+ return `${mq.topic.name}@${mq.broker.name}@${mq.queueId}`;
454
+ }
455
+
456
+ #dropProcessQueue(mqKey: string) {
457
+ const entry = this.#processQueueTable.get(mqKey);
458
+ if (entry) {
459
+ entry.pq.drop();
460
+ this.#processQueueTable.delete(mqKey);
461
+ }
462
+ }
463
+
464
+ #createProcessQueue(mq: MessageQueue, filterExpression: FilterExpression): ProcessQueue | null {
465
+ const mqKey = this.#mqKey(mq);
466
+ if (this.#processQueueTable.has(mqKey)) {
467
+ return null;
468
+ }
469
+ const processQueue = new ProcessQueue(this, mq, filterExpression);
470
+ this.#processQueueTable.set(mqKey, { mq, pq: processQueue });
471
+ return processQueue;
472
+ }
473
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Licensed to the Apache Software Foundation (ASF) under one or more
3
+ * contributor license agreements. See the NOTICE file distributed with
4
+ * this work for additional information regarding copyright ownership.
5
+ * The ASF licenses this file to You under the Apache License, Version 2.0
6
+ * (the "License"); you may not use this file except in compliance with
7
+ * the License. You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import {
19
+ Settings as SettingsPB,
20
+ ClientType,
21
+ Subscription,
22
+ RetryPolicy as RetryPolicyPB,
23
+ } from '../../proto/apache/rocketmq/v2/definition_pb';
24
+ import { Endpoints } from '../route';
25
+ import { Settings, UserAgent } from '../client';
26
+ import { ExponentialBackoffRetryPolicy, RetryPolicy } from '../retry';
27
+ import { createDuration, createResource } from '../util';
28
+ import { FilterExpression } from './FilterExpression';
29
+
30
+ export class PushSubscriptionSettings extends Settings {
31
+ readonly #group: string;
32
+ readonly #subscriptionExpressions: Map<string, FilterExpression>;
33
+ #fifo = false;
34
+ #receiveBatchSize = 32;
35
+ #longPollingTimeout = 30000; // ms
36
+
37
+ constructor(
38
+ namespace: string,
39
+ clientId: string,
40
+ accessPoint: Endpoints,
41
+ consumerGroup: string,
42
+ requestTimeout: number,
43
+ subscriptionExpressions: Map<string, FilterExpression>,
44
+ longPollingTimeout?: number,
45
+ ) {
46
+ super(namespace, clientId, ClientType.PUSH_CONSUMER, accessPoint, requestTimeout);
47
+ this.#group = consumerGroup;
48
+ this.#subscriptionExpressions = subscriptionExpressions;
49
+ if (longPollingTimeout !== undefined) {
50
+ this.#longPollingTimeout = longPollingTimeout;
51
+ }
52
+ }
53
+
54
+ isFifo(): boolean {
55
+ return this.#fifo;
56
+ }
57
+
58
+ getReceiveBatchSize(): number {
59
+ return this.#receiveBatchSize;
60
+ }
61
+
62
+ getLongPollingTimeout(): number {
63
+ return this.#longPollingTimeout;
64
+ }
65
+
66
+ getRetryPolicy(): RetryPolicy | undefined {
67
+ return this.retryPolicy;
68
+ }
69
+
70
+ toProtobuf(): SettingsPB {
71
+ const subscription = new Subscription()
72
+ .setGroup(createResource(this.#group));
73
+
74
+ for (const [ topic, filterExpression ] of this.#subscriptionExpressions.entries()) {
75
+ subscription.addSubscriptions()
76
+ .setTopic(createResource(topic))
77
+ .setExpression(filterExpression.toProtobuf());
78
+ }
79
+
80
+ return new SettingsPB()
81
+ .setClientType(this.clientType)
82
+ .setAccessPoint(this.accessPoint.toProtobuf())
83
+ .setRequestTimeout(createDuration(this.requestTimeout))
84
+ .setSubscription(subscription)
85
+ .setUserAgent(UserAgent.INSTANCE.toProtobuf());
86
+ }
87
+
88
+ sync(settings: SettingsPB): void {
89
+ if (settings.getPubSubCase() !== SettingsPB.PubSubCase.SUBSCRIPTION) {
90
+ return;
91
+ }
92
+ const subscription = settings.getSubscription();
93
+ if (subscription) {
94
+ this.#fifo = subscription.getFifo() ?? false;
95
+ this.#receiveBatchSize = subscription.getReceiveBatchSize() ?? 32;
96
+ const longPollingTimeout = subscription.getLongPollingTimeout();
97
+ if (longPollingTimeout) {
98
+ this.#longPollingTimeout = longPollingTimeout.getSeconds() * 1000 +
99
+ Math.floor(longPollingTimeout.getNanos() / 1000000);
100
+ }
101
+ }
102
+ const backoffPolicy = settings.getBackoffPolicy();
103
+ if (backoffPolicy) {
104
+ switch (backoffPolicy.getStrategyCase()) {
105
+ case RetryPolicyPB.StrategyCase.EXPONENTIAL_BACKOFF: {
106
+ const exponential = backoffPolicy.getExponentialBackoff()!.toObject();
107
+ this.retryPolicy = new ExponentialBackoffRetryPolicy(
108
+ backoffPolicy.getMaxAttempts(),
109
+ exponential.initial?.seconds,
110
+ exponential.max?.seconds,
111
+ exponential.multiplier,
112
+ );
113
+ break;
114
+ }
115
+ case RetryPolicyPB.StrategyCase.CUSTOMIZED_BACKOFF:
116
+ // CustomizedBackoffRetryPolicy not yet implemented in Node.js
117
+ break;
118
+ default:
119
+ break;
120
+ }
121
+ }
122
+ }
123
+ }