pulsar-client 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,6 +43,7 @@ Compatibility between each version of the Node.js client and the C++ client is a
43
43
  | Node.js client | C++ client |
44
44
  |----------------|----------------|
45
45
  | 1.0.0 | 2.3.0 or later |
46
+ | 1.1.0 | 2.4.0 or later |
46
47
 
47
48
  If an incompatible version of the C++ client is installed, you may fail to build or run this library.
48
49
 
@@ -54,6 +55,12 @@ If an incompatible version of the C++ client is installed, you may fail to build
54
55
  $ npm install pulsar-client
55
56
  ```
56
57
 
58
+ ## Typescript Definitions
59
+
60
+ ```shell
61
+ $ npm install @types/pulsar-client --save-dev
62
+ ```
63
+
57
64
  ## Sample code
58
65
 
59
66
  Please refer to [examples](https://github.com/apache/pulsar-client-node/tree/master/examples).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulsar-client",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Pulsar Node.js client",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -27,7 +27,7 @@
27
27
  "license": "Apache-2.0",
28
28
  "gypfile": true,
29
29
  "engines": {
30
- "node": ">=10.0.0"
30
+ "node": ">=10.16.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "clang-format": "^1.2.4",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "bindings": "^1.3.1",
48
- "node-addon-api": "^1.6.2",
48
+ "node-addon-api": "^2.0.0",
49
49
  "node-gyp": "^3.8.0",
50
50
  "node-pre-gyp": "^0.12.0"
51
51
  },
package/src/Consumer.cc CHANGED
@@ -34,6 +34,8 @@ void Consumer::Init(Napi::Env env, Napi::Object exports) {
34
34
  InstanceMethod("receive", &Consumer::Receive),
35
35
  InstanceMethod("acknowledge", &Consumer::Acknowledge),
36
36
  InstanceMethod("acknowledgeId", &Consumer::AcknowledgeId),
37
+ InstanceMethod("negativeAcknowledge", &Consumer::NegativeAcknowledge),
38
+ InstanceMethod("negativeAcknowledgeId", &Consumer::NegativeAcknowledgeId),
37
39
  InstanceMethod("acknowledgeCumulative", &Consumer::AcknowledgeCumulative),
38
40
  InstanceMethod("acknowledgeCumulativeId", &Consumer::AcknowledgeCumulativeId),
39
41
  InstanceMethod("close", &Consumer::Close),
@@ -43,18 +45,20 @@ void Consumer::Init(Napi::Env env, Napi::Object exports) {
43
45
  constructor.SuppressDestruct();
44
46
  }
45
47
 
46
- void Consumer::SetCConsumer(pulsar_consumer_t *cConsumer) { this->cConsumer = cConsumer; }
48
+ void Consumer::SetCConsumer(std::shared_ptr<CConsumerWrapper> cConsumer) { this->wrapper = cConsumer; }
49
+ void Consumer::SetListenerCallback(ListenerCallback *listener) { this->listener = listener; }
47
50
 
48
- Consumer::Consumer(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Consumer>(info) {}
51
+ Consumer::Consumer(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Consumer>(info), listener(nullptr) {}
49
52
 
50
53
  class ConsumerNewInstanceWorker : public Napi::AsyncWorker {
51
54
  public:
52
55
  ConsumerNewInstanceWorker(const Napi::Promise::Deferred &deferred, pulsar_client_t *cClient,
53
- ConsumerConfig *consumerConfig)
56
+ ConsumerConfig *consumerConfig, std::shared_ptr<CConsumerWrapper> consumerWrapper)
54
57
  : AsyncWorker(Napi::Function::New(deferred.Promise().Env(), [](const Napi::CallbackInfo &info) {})),
55
58
  deferred(deferred),
56
59
  cClient(cClient),
57
- consumerConfig(consumerConfig) {}
60
+ consumerConfig(consumerConfig),
61
+ consumerWrapper(consumerWrapper) {}
58
62
  ~ConsumerNewInstanceWorker() {}
59
63
  void Execute() {
60
64
  const std::string &topic = this->consumerConfig->GetTopic();
@@ -75,20 +79,29 @@ class ConsumerNewInstanceWorker : public Napi::AsyncWorker {
75
79
  SetError(msg);
76
80
  return;
77
81
  }
82
+ int32_t nAckRedeliverTimeoutMs = this->consumerConfig->GetNAckRedeliverTimeoutMs();
83
+ if (nAckRedeliverTimeoutMs < 0) {
84
+ std::string msg("NAck timeout should be greater than or equal to zero");
85
+ SetError(msg);
86
+ return;
87
+ }
78
88
 
79
- pulsar_result result =
80
- pulsar_client_subscribe(this->cClient, topic.c_str(), subscription.c_str(),
81
- this->consumerConfig->GetCConsumerConfig(), &(this->cConsumer));
82
- delete this->consumerConfig;
89
+ pulsar_result result = pulsar_client_subscribe(this->cClient, topic.c_str(), subscription.c_str(),
90
+ this->consumerConfig->GetCConsumerConfig(),
91
+ &this->consumerWrapper->cConsumer);
83
92
  if (result != pulsar_result_Ok) {
84
93
  SetError(std::string("Failed to create consumer: ") + pulsar_result_str(result));
85
- return;
94
+ } else {
95
+ this->listener = this->consumerConfig->GetListenerCallback();
86
96
  }
97
+
98
+ delete this->consumerConfig;
87
99
  }
88
100
  void OnOK() {
89
101
  Napi::Object obj = Consumer::constructor.New({});
90
102
  Consumer *consumer = Consumer::Unwrap(obj);
91
- consumer->SetCConsumer(this->cConsumer);
103
+ consumer->SetCConsumer(this->consumerWrapper);
104
+ consumer->SetListenerCallback(this->listener);
92
105
  this->deferred.Resolve(obj);
93
106
  }
94
107
  void OnError(const Napi::Error &e) { this->deferred.Reject(Napi::Error::New(Env(), e.Message()).Value()); }
@@ -96,15 +109,19 @@ class ConsumerNewInstanceWorker : public Napi::AsyncWorker {
96
109
  private:
97
110
  Napi::Promise::Deferred deferred;
98
111
  pulsar_client_t *cClient;
99
- ConsumerConfig *consumerConfig;
100
112
  pulsar_consumer_t *cConsumer;
113
+ ConsumerConfig *consumerConfig;
114
+ ListenerCallback *listener;
115
+ std::shared_ptr<CConsumerWrapper> consumerWrapper;
101
116
  };
102
117
 
103
118
  Napi::Value Consumer::NewInstance(const Napi::CallbackInfo &info, pulsar_client_t *cClient) {
104
119
  Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());
105
120
  Napi::Object config = info[0].As<Napi::Object>();
106
- ConsumerConfig *consumerConfig = new ConsumerConfig(config);
107
- ConsumerNewInstanceWorker *wk = new ConsumerNewInstanceWorker(deferred, cClient, consumerConfig);
121
+ std::shared_ptr<CConsumerWrapper> consumerWrapper = std::make_shared<CConsumerWrapper>();
122
+ ConsumerConfig *consumerConfig = new ConsumerConfig(config, consumerWrapper);
123
+ ConsumerNewInstanceWorker *wk =
124
+ new ConsumerNewInstanceWorker(deferred, cClient, consumerConfig, consumerWrapper);
108
125
  wk->Queue();
109
126
  return deferred.Promise();
110
127
  }
@@ -146,11 +163,12 @@ class ConsumerReceiveWorker : public Napi::AsyncWorker {
146
163
  Napi::Value Consumer::Receive(const Napi::CallbackInfo &info) {
147
164
  Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());
148
165
  if (info[0].IsUndefined()) {
149
- ConsumerReceiveWorker *wk = new ConsumerReceiveWorker(deferred, this->cConsumer);
166
+ ConsumerReceiveWorker *wk = new ConsumerReceiveWorker(deferred, this->wrapper->cConsumer);
150
167
  wk->Queue();
151
168
  } else {
152
169
  Napi::Number timeout = info[0].As<Napi::Object>().ToNumber();
153
- ConsumerReceiveWorker *wk = new ConsumerReceiveWorker(deferred, this->cConsumer, timeout.Int64Value());
170
+ ConsumerReceiveWorker *wk =
171
+ new ConsumerReceiveWorker(deferred, this->wrapper->cConsumer, timeout.Int64Value());
154
172
  wk->Queue();
155
173
  }
156
174
  return deferred.Promise();
@@ -159,25 +177,38 @@ Napi::Value Consumer::Receive(const Napi::CallbackInfo &info) {
159
177
  void Consumer::Acknowledge(const Napi::CallbackInfo &info) {
160
178
  Napi::Object obj = info[0].As<Napi::Object>();
161
179
  Message *msg = Message::Unwrap(obj);
162
- pulsar_consumer_acknowledge_async(this->cConsumer, msg->GetCMessage(), NULL, NULL);
180
+ pulsar_consumer_acknowledge_async(this->wrapper->cConsumer, msg->GetCMessage(), NULL, NULL);
163
181
  }
164
182
 
165
183
  void Consumer::AcknowledgeId(const Napi::CallbackInfo &info) {
166
184
  Napi::Object obj = info[0].As<Napi::Object>();
167
185
  MessageId *msgId = MessageId::Unwrap(obj);
168
- pulsar_consumer_acknowledge_async_id(this->cConsumer, msgId->GetCMessageId(), NULL, NULL);
186
+ pulsar_consumer_acknowledge_async_id(this->wrapper->cConsumer, msgId->GetCMessageId(), NULL, NULL);
187
+ }
188
+
189
+ void Consumer::NegativeAcknowledge(const Napi::CallbackInfo &info) {
190
+ Napi::Object obj = info[0].As<Napi::Object>();
191
+ Message *msg = Message::Unwrap(obj);
192
+ pulsar_consumer_negative_acknowledge(this->wrapper->cConsumer, msg->GetCMessage());
193
+ }
194
+
195
+ void Consumer::NegativeAcknowledgeId(const Napi::CallbackInfo &info) {
196
+ Napi::Object obj = info[0].As<Napi::Object>();
197
+ MessageId *msgId = MessageId::Unwrap(obj);
198
+ pulsar_consumer_negative_acknowledge_id(this->wrapper->cConsumer, msgId->GetCMessageId());
169
199
  }
170
200
 
171
201
  void Consumer::AcknowledgeCumulative(const Napi::CallbackInfo &info) {
172
202
  Napi::Object obj = info[0].As<Napi::Object>();
173
203
  Message *msg = Message::Unwrap(obj);
174
- pulsar_consumer_acknowledge_cumulative_async(this->cConsumer, msg->GetCMessage(), NULL, NULL);
204
+ pulsar_consumer_acknowledge_cumulative_async(this->wrapper->cConsumer, msg->GetCMessage(), NULL, NULL);
175
205
  }
176
206
 
177
207
  void Consumer::AcknowledgeCumulativeId(const Napi::CallbackInfo &info) {
178
208
  Napi::Object obj = info[0].As<Napi::Object>();
179
209
  MessageId *msgId = MessageId::Unwrap(obj);
180
- pulsar_consumer_acknowledge_cumulative_async_id(this->cConsumer, msgId->GetCMessageId(), NULL, NULL);
210
+ pulsar_consumer_acknowledge_cumulative_async_id(this->wrapper->cConsumer, msgId->GetCMessageId(), NULL,
211
+ NULL);
181
212
  }
182
213
 
183
214
  class ConsumerCloseWorker : public Napi::AsyncWorker {
@@ -188,6 +219,7 @@ class ConsumerCloseWorker : public Napi::AsyncWorker {
188
219
  cConsumer(cConsumer) {}
189
220
  ~ConsumerCloseWorker() {}
190
221
  void Execute() {
222
+ pulsar_consumer_pause_message_listener(this->cConsumer);
191
223
  pulsar_result result = pulsar_consumer_close(this->cConsumer);
192
224
  if (result != pulsar_result_Ok) SetError(pulsar_result_str(result));
193
225
  }
@@ -204,9 +236,14 @@ class ConsumerCloseWorker : public Napi::AsyncWorker {
204
236
 
205
237
  Napi::Value Consumer::Close(const Napi::CallbackInfo &info) {
206
238
  Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());
207
- ConsumerCloseWorker *wk = new ConsumerCloseWorker(deferred, this->cConsumer);
239
+ ConsumerCloseWorker *wk = new ConsumerCloseWorker(deferred, this->wrapper->cConsumer);
208
240
  wk->Queue();
209
241
  return deferred.Promise();
210
242
  }
211
243
 
212
- Consumer::~Consumer() { pulsar_consumer_free(this->cConsumer); }
244
+ Consumer::~Consumer() {
245
+ if (this->listener) {
246
+ pulsar_consumer_pause_message_listener(this->wrapper->cConsumer);
247
+ this->listener->callback.Release();
248
+ }
249
+ }
package/src/Consumer.h CHANGED
@@ -22,6 +22,7 @@
22
22
 
23
23
  #include <napi.h>
24
24
  #include <pulsar/c/client.h>
25
+ #include "ConsumerConfig.h"
25
26
 
26
27
  class Consumer : public Napi::ObjectWrap<Consumer> {
27
28
  public:
@@ -30,14 +31,18 @@ class Consumer : public Napi::ObjectWrap<Consumer> {
30
31
  static Napi::FunctionReference constructor;
31
32
  Consumer(const Napi::CallbackInfo &info);
32
33
  ~Consumer();
33
- void SetCConsumer(pulsar_consumer_t *cConsumer);
34
+ void SetCConsumer(std::shared_ptr<CConsumerWrapper> cConsumer);
35
+ void SetListenerCallback(ListenerCallback *listener);
34
36
 
35
37
  private:
36
- pulsar_consumer_t *cConsumer;
38
+ std::shared_ptr<CConsumerWrapper> wrapper;
39
+ ListenerCallback *listener;
37
40
 
38
41
  Napi::Value Receive(const Napi::CallbackInfo &info);
39
42
  void Acknowledge(const Napi::CallbackInfo &info);
40
43
  void AcknowledgeId(const Napi::CallbackInfo &info);
44
+ void NegativeAcknowledge(const Napi::CallbackInfo &info);
45
+ void NegativeAcknowledgeId(const Napi::CallbackInfo &info);
41
46
  void AcknowledgeCumulative(const Napi::CallbackInfo &info);
42
47
  void AcknowledgeCumulativeId(const Napi::CallbackInfo &info);
43
48
  Napi::Value Close(const Napi::CallbackInfo &info);
@@ -18,25 +18,65 @@
18
18
  */
19
19
 
20
20
  #include "ConsumerConfig.h"
21
+ #include "Consumer.h"
22
+ #include "Message.h"
21
23
  #include <pulsar/c/consumer_configuration.h>
24
+ #include <pulsar/c/consumer.h>
22
25
  #include <map>
23
26
 
24
27
  static const std::string CFG_TOPIC = "topic";
25
28
  static const std::string CFG_SUBSCRIPTION = "subscription";
26
29
  static const std::string CFG_SUBSCRIPTION_TYPE = "subscriptionType";
30
+ static const std::string CFG_INIT_POSITION = "subscriptionInitialPosition";
27
31
  static const std::string CFG_ACK_TIMEOUT = "ackTimeoutMs";
32
+ static const std::string CFG_NACK_REDELIVER_TIMEOUT = "nAckRedeliverTimeoutMs";
28
33
  static const std::string CFG_RECV_QUEUE = "receiverQueueSize";
29
34
  static const std::string CFG_RECV_QUEUE_ACROSS_PARTITIONS = "receiverQueueSizeAcrossPartitions";
30
35
  static const std::string CFG_CONSUMER_NAME = "consumerName";
31
36
  static const std::string CFG_PROPS = "properties";
37
+ static const std::string CFG_LISTENER = "listener";
32
38
 
33
39
  static const std::map<std::string, pulsar_consumer_type> SUBSCRIPTION_TYPE = {
34
40
  {"Exclusive", pulsar_ConsumerExclusive},
35
41
  {"Shared", pulsar_ConsumerShared},
36
42
  {"Failover", pulsar_ConsumerFailover}};
37
43
 
38
- ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig)
39
- : topic(""), subscription(""), ackTimeoutMs(0) {
44
+ static const std::map<std::string, initial_position> INIT_POSITION = {
45
+ {"Latest", initial_position_latest}, {"Earliest", initial_position_earliest}};
46
+
47
+ struct MessageListenerProxyData {
48
+ std::shared_ptr<CConsumerWrapper> consumerWrapper;
49
+ pulsar_message_t *cMessage;
50
+
51
+ MessageListenerProxyData(std::shared_ptr<CConsumerWrapper> consumerWrapper, pulsar_message_t *cMessage)
52
+ : consumerWrapper(consumerWrapper), cMessage(cMessage) {}
53
+ };
54
+
55
+ void MessageListenerProxy(Napi::Env env, Napi::Function jsCallback, MessageListenerProxyData *data) {
56
+ Napi::Object msg = Message::NewInstance({}, data->cMessage);
57
+ Napi::Object consumerObj = Consumer::constructor.New({});
58
+ Consumer *consumer = Consumer::Unwrap(consumerObj);
59
+ consumer->SetCConsumer(std::move(data->consumerWrapper));
60
+ delete data;
61
+ jsCallback.Call({msg, consumerObj});
62
+ }
63
+
64
+ void MessageListener(pulsar_consumer_t *cConsumer, pulsar_message_t *cMessage, void *ctx) {
65
+ ListenerCallback *listenerCallback = (ListenerCallback *)ctx;
66
+ if (listenerCallback->callback.Acquire() != napi_ok) {
67
+ return;
68
+ };
69
+ MessageListenerProxyData *dataPtr =
70
+ new MessageListenerProxyData(listenerCallback->consumerWrapper, cMessage);
71
+ listenerCallback->callback.BlockingCall(dataPtr, MessageListenerProxy);
72
+ listenerCallback->callback.Release();
73
+ }
74
+
75
+ void FinalizeListenerCallback(Napi::Env env, ListenerCallback *cb, void *) { delete cb; }
76
+
77
+ ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig,
78
+ std::shared_ptr<CConsumerWrapper> consumerWrapper)
79
+ : topic(""), subscription(""), ackTimeoutMs(0), nAckRedeliverTimeoutMs(60000), listener(nullptr) {
40
80
  this->cConsumerConfig = pulsar_consumer_configuration_create();
41
81
 
42
82
  if (consumerConfig.Has(CFG_TOPIC) && consumerConfig.Get(CFG_TOPIC).IsString()) {
@@ -55,6 +95,14 @@ ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig)
55
95
  }
56
96
  }
57
97
 
98
+ if (consumerConfig.Has(CFG_INIT_POSITION) && consumerConfig.Get(CFG_INIT_POSITION).IsString()) {
99
+ std::string initPosition = consumerConfig.Get(CFG_INIT_POSITION).ToString().Utf8Value();
100
+ if (INIT_POSITION.count(initPosition)) {
101
+ pulsar_consumer_set_subscription_initial_position(this->cConsumerConfig,
102
+ INIT_POSITION.at(initPosition));
103
+ }
104
+ }
105
+
58
106
  if (consumerConfig.Has(CFG_CONSUMER_NAME) && consumerConfig.Get(CFG_CONSUMER_NAME).IsString()) {
59
107
  std::string consumerName = consumerConfig.Get(CFG_CONSUMER_NAME).ToString().Utf8Value();
60
108
  if (!consumerName.empty()) pulsar_consumer_set_consumer_name(this->cConsumerConfig, consumerName.c_str());
@@ -67,6 +115,15 @@ ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig)
67
115
  }
68
116
  }
69
117
 
118
+ if (consumerConfig.Has(CFG_NACK_REDELIVER_TIMEOUT) &&
119
+ consumerConfig.Get(CFG_NACK_REDELIVER_TIMEOUT).IsNumber()) {
120
+ this->nAckRedeliverTimeoutMs = consumerConfig.Get(CFG_NACK_REDELIVER_TIMEOUT).ToNumber().Int64Value();
121
+ if (this->nAckRedeliverTimeoutMs >= 0) {
122
+ pulsar_configure_set_negative_ack_redelivery_delay_ms(this->cConsumerConfig,
123
+ this->nAckRedeliverTimeoutMs);
124
+ }
125
+ }
126
+
70
127
  if (consumerConfig.Has(CFG_RECV_QUEUE) && consumerConfig.Get(CFG_RECV_QUEUE).IsNumber()) {
71
128
  int32_t receiverQueueSize = consumerConfig.Get(CFG_RECV_QUEUE).ToNumber().Int32Value();
72
129
  if (receiverQueueSize >= 0) {
@@ -94,12 +151,43 @@ ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig)
94
151
  pulsar_consumer_configuration_set_property(this->cConsumerConfig, key.c_str(), value.c_str());
95
152
  }
96
153
  }
154
+
155
+ if (consumerConfig.Has(CFG_LISTENER) && consumerConfig.Get(CFG_LISTENER).IsFunction()) {
156
+ this->listener = new ListenerCallback();
157
+ this->listener->consumerWrapper = consumerWrapper;
158
+ Napi::ThreadSafeFunction callback = Napi::ThreadSafeFunction::New(
159
+ consumerConfig.Env(), consumerConfig.Get(CFG_LISTENER).As<Napi::Function>(), "Listener Callback", 1,
160
+ 1, (void *)NULL, FinalizeListenerCallback, listener);
161
+ this->listener->callback = std::move(callback);
162
+ pulsar_consumer_configuration_set_message_listener(this->cConsumerConfig, &MessageListener,
163
+ this->listener);
164
+ }
97
165
  }
98
166
 
99
- ConsumerConfig::~ConsumerConfig() { pulsar_consumer_configuration_free(this->cConsumerConfig); }
167
+ ConsumerConfig::~ConsumerConfig() {
168
+ pulsar_consumer_configuration_free(this->cConsumerConfig);
169
+ if (this->listener) {
170
+ this->listener->callback.Release();
171
+ }
172
+ }
100
173
 
101
174
  pulsar_consumer_configuration_t *ConsumerConfig::GetCConsumerConfig() { return this->cConsumerConfig; }
102
175
 
103
176
  std::string ConsumerConfig::GetTopic() { return this->topic; }
104
177
  std::string ConsumerConfig::GetSubscription() { return this->subscription; }
178
+ ListenerCallback *ConsumerConfig::GetListenerCallback() {
179
+ ListenerCallback *cb = this->listener;
180
+ this->listener = nullptr;
181
+ return cb;
182
+ }
183
+
105
184
  int64_t ConsumerConfig::GetAckTimeoutMs() { return this->ackTimeoutMs; }
185
+ int64_t ConsumerConfig::GetNAckRedeliverTimeoutMs() { return this->nAckRedeliverTimeoutMs; }
186
+
187
+ CConsumerWrapper::CConsumerWrapper() : cConsumer(nullptr) {}
188
+
189
+ CConsumerWrapper::~CConsumerWrapper() {
190
+ if (this->cConsumer) {
191
+ pulsar_consumer_free(this->cConsumer);
192
+ }
193
+ }
@@ -25,20 +25,35 @@
25
25
 
26
26
  #define MIN_ACK_TIMEOUT_MILLIS 10000
27
27
 
28
+ struct CConsumerWrapper {
29
+ pulsar_consumer_t *cConsumer;
30
+ CConsumerWrapper();
31
+ ~CConsumerWrapper();
32
+ };
33
+
34
+ struct ListenerCallback {
35
+ Napi::ThreadSafeFunction callback;
36
+ std::shared_ptr<CConsumerWrapper> consumerWrapper;
37
+ };
38
+
28
39
  class ConsumerConfig {
29
40
  public:
30
- ConsumerConfig(const Napi::Object &consumerConfig);
41
+ ConsumerConfig(const Napi::Object &consumerConfig, std::shared_ptr<CConsumerWrapper> consumerWrapper);
31
42
  ~ConsumerConfig();
32
43
  pulsar_consumer_configuration_t *GetCConsumerConfig();
33
44
  std::string GetTopic();
34
45
  std::string GetSubscription();
35
46
  int64_t GetAckTimeoutMs();
47
+ int64_t GetNAckRedeliverTimeoutMs();
48
+ ListenerCallback *GetListenerCallback();
36
49
 
37
50
  private:
38
51
  pulsar_consumer_configuration_t *cConsumerConfig;
39
52
  std::string topic;
40
53
  std::string subscription;
41
54
  int64_t ackTimeoutMs;
55
+ int64_t nAckRedeliverTimeoutMs;
56
+ ListenerCallback *listener;
42
57
  };
43
58
 
44
59
  #endif
package/src/Message.cc CHANGED
@@ -181,9 +181,7 @@ pulsar_message_t *Message::BuildMessage(Napi::Object conf) {
181
181
  for (int i = 0; i < length; i++) {
182
182
  SetString(arr, clusters.Get(i).ToString().Utf8Value().c_str(), i);
183
183
  }
184
- // TODO: temoporalily commented out unless 2.3.1 which includes interface change of
185
- // pulsar_message_set_replication_clusters (#3729) is released
186
- // pulsar_message_set_replication_clusters(cMessage, (const char **)arr, length);
184
+ pulsar_message_set_replication_clusters(cMessage, (const char **)arr, length);
187
185
  FreeStringArray(arr, length);
188
186
  }
189
187
  }
package/src/MessageId.cc CHANGED
@@ -29,7 +29,7 @@ Napi::Object MessageId::Init(Napi::Env env, Napi::Object exports) {
29
29
  Napi::Function func = DefineClass(env, "MessageId",
30
30
  {StaticMethod("earliest", &MessageId::Earliest, napi_static),
31
31
  StaticMethod("latest", &MessageId::Latest, napi_static),
32
- StaticMethod("finalize", &MessageId::Finalize, napi_static),
32
+ StaticMethod("finalize", &MessageId::Free, napi_static),
33
33
  InstanceMethod("serialize", &MessageId::Serialize),
34
34
  StaticMethod("deserialize", &MessageId::Deserialize, napi_static),
35
35
  InstanceMethod("toString", &MessageId::ToString)});
@@ -58,7 +58,7 @@ Napi::Object MessageId::NewInstance(Napi::Value arg) {
58
58
  return obj;
59
59
  }
60
60
 
61
- void MessageId::Finalize(const Napi::CallbackInfo &info) {
61
+ void MessageId::Free(const Napi::CallbackInfo &info) {
62
62
  Napi::Object obj = info[0].As<Napi::Object>();
63
63
  MessageId *msgId = Unwrap(obj);
64
64
  pulsar_message_id_free(msgId->cMessageId);
package/src/MessageId.h CHANGED
@@ -33,7 +33,7 @@ class MessageId : public Napi::ObjectWrap<MessageId> {
33
33
  static Napi::Value Latest(const Napi::CallbackInfo &info);
34
34
  Napi::Value Serialize(const Napi::CallbackInfo &info);
35
35
  static Napi::Value Deserialize(const Napi::CallbackInfo &info);
36
- static void Finalize(const Napi::CallbackInfo &info);
36
+ static void Free(const Napi::CallbackInfo &info);
37
37
  MessageId(const Napi::CallbackInfo &info);
38
38
  ~MessageId();
39
39
  pulsar_message_id_t *GetCMessageId();
@@ -30,6 +30,7 @@ const Pulsar = require('../index.js');
30
30
  await expect(client.subscribe({
31
31
  subscription: 'sub1',
32
32
  ackTimeoutMs: 10000,
33
+ nAckRedeliverTimeoutMs: 60000,
33
34
  })).rejects.toThrow('Topic is required and must be specified as a string when creating consumer');
34
35
  });
35
36
 
@@ -38,6 +39,7 @@ const Pulsar = require('../index.js');
38
39
  topic: 0,
39
40
  subscription: 'sub1',
40
41
  ackTimeoutMs: 10000,
42
+ nAckRedeliverTimeoutMs: 60000,
41
43
  })).rejects.toThrow('Topic is required and must be specified as a string when creating consumer');
42
44
  });
43
45
 
@@ -45,6 +47,7 @@ const Pulsar = require('../index.js');
45
47
  await expect(client.subscribe({
46
48
  topic: 'persistent://public/default/t1',
47
49
  ackTimeoutMs: 10000,
50
+ nAckRedeliverTimeoutMs: 60000,
48
51
  })).rejects.toThrow('Subscription is required and must be specified as a string when creating consumer');
49
52
  });
50
53
 
@@ -53,6 +56,7 @@ const Pulsar = require('../index.js');
53
56
  topic: 'persistent://public/default/t1',
54
57
  subscription: 0,
55
58
  ackTimeoutMs: 10000,
59
+ nAckRedeliverTimeoutMs: 60000,
56
60
  })).rejects.toThrow('Subscription is required and must be specified as a string when creating consumer');
57
61
  });
58
62
 
@@ -61,6 +65,7 @@ const Pulsar = require('../index.js');
61
65
  topic: 'persistent://no-tenant/namespace/topic',
62
66
  subscription: 'sub1',
63
67
  ackTimeoutMs: 10000,
68
+ nAckRedeliverTimeoutMs: 60000,
64
69
  })).rejects.toThrow('Failed to create consumer: ConnectError');
65
70
  });
66
71
 
@@ -69,8 +74,18 @@ const Pulsar = require('../index.js');
69
74
  topic: 'persistent://public/no-namespace/topic',
70
75
  subscription: 'sub1',
71
76
  ackTimeoutMs: 10000,
77
+ nAckRedeliverTimeoutMs: 60000,
72
78
  })).rejects.toThrow('Failed to create consumer: ConnectError');
73
79
  });
80
+
81
+ test('Not Positive NAckRedeliverTimeout', async () => {
82
+ await expect(client.subscribe({
83
+ topic: 'persistent://public/default/t1',
84
+ subscription: 'sub1',
85
+ ackTimeoutMs: 10000,
86
+ nAckRedeliverTimeoutMs: -12,
87
+ })).rejects.toThrow('NAck timeout should be greater than or equal to zero');
88
+ });
74
89
  });
75
90
  });
76
91
  })();
@@ -67,6 +67,107 @@ const Pulsar = require('../index.js');
67
67
  await client.close();
68
68
  });
69
69
 
70
+ test('negativeAcknowledge', async () => {
71
+ const client = new Pulsar.Client({
72
+ serviceUrl: 'pulsar://localhost:6650',
73
+ operationTimeoutSeconds: 30,
74
+ });
75
+
76
+ const topic = 'persistent://public/default/produce-consume';
77
+ const producer = await client.createProducer({
78
+ topic,
79
+ sendTimeoutMs: 30000,
80
+ batchingEnabled: true,
81
+ });
82
+ expect(producer).not.toBeNull();
83
+
84
+ const consumer = await client.subscribe({
85
+ topic,
86
+ subscription: 'sub1',
87
+ ackTimeoutMs: 10000,
88
+ nAckRedeliverTimeoutMs: 1000,
89
+ });
90
+
91
+ expect(consumer).not.toBeNull();
92
+
93
+ const message = 'my-message';
94
+ producer.send({
95
+ data: Buffer.from(message),
96
+ });
97
+ await producer.flush();
98
+
99
+ const results = [];
100
+ const msg = await consumer.receive();
101
+ results.push(msg.getData().toString());
102
+ consumer.negativeAcknowledge(msg);
103
+
104
+ const msg2 = await consumer.receive();
105
+ results.push(msg2.getData().toString());
106
+ consumer.acknowledge(msg2);
107
+
108
+ await expect(consumer.receive(1000)).rejects.toThrow(
109
+ 'Failed to received message TimeOut',
110
+ );
111
+
112
+ expect(results).toEqual([message, message]);
113
+
114
+ await producer.close();
115
+ await consumer.close();
116
+ await client.close();
117
+ });
118
+
119
+ test('Produce/Consume Listener', async () => {
120
+ const client = new Pulsar.Client({
121
+ serviceUrl: 'pulsar://localhost:6650',
122
+ operationTimeoutSeconds: 30,
123
+ });
124
+
125
+ const topic = 'persistent://public/default/produce-consume-listener';
126
+ const producer = await client.createProducer({
127
+ topic,
128
+ sendTimeoutMs: 30000,
129
+ batchingEnabled: true,
130
+ });
131
+ expect(producer).not.toBeNull();
132
+
133
+ let finish;
134
+ const results = [];
135
+ const finishPromise = new Promise((resolve) => {
136
+ finish = resolve;
137
+ });
138
+
139
+ const consumer = await client.subscribe({
140
+ topic,
141
+ subscription: 'sub1',
142
+ ackTimeoutMs: 10000,
143
+ listener: (message, messageConsumer) => {
144
+ const data = message.getData().toString();
145
+ results.push(data);
146
+ messageConsumer.acknowledge(message);
147
+ if (results.length === 10) finish();
148
+ },
149
+ });
150
+
151
+ expect(consumer).not.toBeNull();
152
+
153
+ const messages = [];
154
+ for (let i = 0; i < 10; i += 1) {
155
+ const msg = `my-message-${i}`;
156
+ producer.send({
157
+ data: Buffer.from(msg),
158
+ });
159
+ messages.push(msg);
160
+ }
161
+ await producer.flush();
162
+
163
+ await finishPromise;
164
+ expect(lodash.difference(messages, results)).toEqual([]);
165
+
166
+ await producer.close();
167
+ await consumer.close();
168
+ await client.close();
169
+ });
170
+
70
171
  test('acknowledgeCumulative', async () => {
71
172
  const client = new Pulsar.Client({
72
173
  serviceUrl: 'pulsar://localhost:6650',
@@ -105,13 +206,84 @@ const Pulsar = require('../index.js');
105
206
  }
106
207
  }
107
208
 
108
- await expect(consumer.receive(1000)).rejects.toThrow('Failed to received message TimeOut');
209
+ await expect(consumer.receive(1000)).rejects.toThrow(
210
+ 'Failed to received message TimeOut',
211
+ );
109
212
 
110
213
  await producer.close();
111
214
  await consumer.close();
112
215
  await client.close();
113
216
  });
114
217
 
218
+ test('subscriptionInitialPosition', async () => {
219
+ const client = new Pulsar.Client({
220
+ serviceUrl: 'pulsar://localhost:6650',
221
+ operationTimeoutSeconds: 30,
222
+ });
223
+
224
+ const topic = 'persistent://public/default/subscriptionInitialPosition';
225
+ const producer = await client.createProducer({
226
+ topic,
227
+ sendTimeoutMs: 30000,
228
+ batchingEnabled: false,
229
+ });
230
+ expect(producer).not.toBeNull();
231
+
232
+ const messages = [];
233
+ for (let i = 0; i < 2; i += 1) {
234
+ const msg = `my-message-${i}`;
235
+ producer.send({
236
+ data: Buffer.from(msg),
237
+ });
238
+ messages.push(msg);
239
+ }
240
+ await producer.flush();
241
+
242
+ const latestConsumer = await client.subscribe({
243
+ topic,
244
+ subscription: 'latestSub',
245
+ subscriptionInitialPosition: 'Latest',
246
+ });
247
+ expect(latestConsumer).not.toBeNull();
248
+
249
+ const earliestConsumer = await client.subscribe({
250
+ topic,
251
+ subscription: 'earliestSub',
252
+ subscriptionInitialPosition: 'Earliest',
253
+ });
254
+ expect(earliestConsumer).not.toBeNull();
255
+
256
+ for (let i = 2; i < 4; i += 1) {
257
+ const msg = `my-message-${i}`;
258
+ producer.send({
259
+ data: Buffer.from(msg),
260
+ });
261
+ messages.push(msg);
262
+ }
263
+ await producer.flush();
264
+
265
+ const latestResults = [];
266
+ const earliestResults = [];
267
+ for (let i = 0; i < 4; i += 1) {
268
+ if (i < 2) {
269
+ const latestMsg = await latestConsumer.receive(5000);
270
+ latestConsumer.acknowledge(latestMsg);
271
+ latestResults.push(latestMsg.getData().toString());
272
+ }
273
+
274
+ const earliestMsg = await earliestConsumer.receive(5000);
275
+ earliestConsumer.acknowledge(earliestMsg);
276
+ earliestResults.push(earliestMsg.getData().toString());
277
+ }
278
+ expect(lodash.difference(messages, latestResults)).toEqual(['my-message-0', 'my-message-1']);
279
+ expect(lodash.difference(messages, earliestResults)).toEqual([]);
280
+
281
+ await producer.close();
282
+ await latestConsumer.close();
283
+ await earliestConsumer.close();
284
+ await client.close();
285
+ });
286
+
115
287
  test('Produce/Read', async () => {
116
288
  const client = new Pulsar.Client({
117
289
  serviceUrl: 'pulsar://localhost:6650',