pulsar-client 1.8.2 → 1.9.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/.github/workflows/ci-pr-validation.yml +67 -0
- package/README.md +15 -5
- package/binding.gyp +34 -12
- package/build-support/pulsar-test-service-start.sh +1 -1
- package/docs/release-process.md +11 -0
- package/examples/encryption-reader.js +44 -0
- package/index.d.ts +51 -0
- package/package.json +1 -1
- package/perf/perf_producer.js +2 -2
- package/pulsar-client-cpp.txt +2 -2
- package/src/Client.cc +36 -0
- package/src/Client.h +4 -2
- package/src/Client.js +4 -0
- package/src/Consumer.cc +35 -9
- package/src/Consumer.h +2 -0
- package/src/ConsumerConfig.cc +50 -0
- package/src/LogUtils.h +32 -0
- package/src/ProducerConfig.cc +14 -0
- package/src/Reader.cc +3 -1
- package/src/ReaderConfig.cc +24 -0
- package/tests/client.test.js +69 -0
- package/tests/conf/standalone.conf +3 -0
- package/tests/consumer.test.js +178 -2
- package/tests/end_to_end.test.js +101 -0
- package/tests/http_utils.js +45 -0
- package/tests/producer.test.js +64 -2
- package/tests/reader.test.js +64 -0
- package/tstest.ts +44 -0
|
@@ -237,3 +237,70 @@ jobs:
|
|
|
237
237
|
- name: Package Node binaries lib
|
|
238
238
|
run: |
|
|
239
239
|
npx node-pre-gyp package --target_arch=${{ env.TARGET }}
|
|
240
|
+
|
|
241
|
+
macos-napi-homebrew:
|
|
242
|
+
name: Build NAPI macos with CPP lib installed by Homebrew
|
|
243
|
+
runs-on: macos-latest
|
|
244
|
+
timeout-minutes: 3000
|
|
245
|
+
strategy:
|
|
246
|
+
fail-fast: false
|
|
247
|
+
steps:
|
|
248
|
+
- uses: actions/checkout@v3
|
|
249
|
+
- name: Use Node.js 18
|
|
250
|
+
uses: actions/setup-node@v3
|
|
251
|
+
with:
|
|
252
|
+
node-version: 18
|
|
253
|
+
cache: 'npm'
|
|
254
|
+
- name: Install CPP lib
|
|
255
|
+
run: |
|
|
256
|
+
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true
|
|
257
|
+
brew update
|
|
258
|
+
brew install libpulsar
|
|
259
|
+
- name: Build Node binaries lib
|
|
260
|
+
run: |
|
|
261
|
+
npm install --ignore-scripts
|
|
262
|
+
npx node-pre-gyp configure
|
|
263
|
+
npx node-pre-gyp build
|
|
264
|
+
- name: Test loading Node binaries lib
|
|
265
|
+
run: |
|
|
266
|
+
node pkg/load_test.js
|
|
267
|
+
- name: Package Node binaries lib
|
|
268
|
+
run: |
|
|
269
|
+
npx node-pre-gyp package
|
|
270
|
+
|
|
271
|
+
linux-napi-apt:
|
|
272
|
+
name: Build NAPI linux with CPP lib installed by APT
|
|
273
|
+
runs-on: ubuntu-22.04
|
|
274
|
+
timeout-minutes: 3000
|
|
275
|
+
strategy:
|
|
276
|
+
fail-fast: false
|
|
277
|
+
steps:
|
|
278
|
+
- uses: actions/checkout@v3
|
|
279
|
+
- name: Use Node.js 18
|
|
280
|
+
uses: actions/setup-node@v3
|
|
281
|
+
with:
|
|
282
|
+
node-version: 18
|
|
283
|
+
cache: 'npm'
|
|
284
|
+
- name: Install CPP lib
|
|
285
|
+
run: |
|
|
286
|
+
source pulsar-client-cpp.txt
|
|
287
|
+
UNAME_ARCH=$(uname -m)
|
|
288
|
+
if [ $UNAME_ARCH == 'aarch64' ]; then
|
|
289
|
+
PLATFORM='arm64'
|
|
290
|
+
else
|
|
291
|
+
PLATFORM='x86_64'
|
|
292
|
+
fi
|
|
293
|
+
curl -s -L -O ${CPP_CLIENT_BASE_URL}/deb-${PLATFORM}/apache-pulsar-client.deb
|
|
294
|
+
curl -s -L -O ${CPP_CLIENT_BASE_URL}/deb-${PLATFORM}/apache-pulsar-client-dev.deb
|
|
295
|
+
sudo -E apt -y install ./apache-pulsar-client.deb ./apache-pulsar-client-dev.deb
|
|
296
|
+
- name: Build Node binaries lib
|
|
297
|
+
run: |
|
|
298
|
+
npm install --ignore-scripts
|
|
299
|
+
npx node-pre-gyp configure
|
|
300
|
+
npx node-pre-gyp build
|
|
301
|
+
- name: Test loading Node binaries lib
|
|
302
|
+
run: |
|
|
303
|
+
node pkg/load_test.js
|
|
304
|
+
- name: Package Node binaries lib
|
|
305
|
+
run: |
|
|
306
|
+
npx node-pre-gyp package
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
# Pulsar Node.js client library
|
|
23
23
|
|
|
24
|
-
The Pulsar Node.js client can be used to create Pulsar producers and consumers in Node.js.
|
|
24
|
+
The Pulsar Node.js client can be used to create Pulsar producers and consumers in Node.js. For the supported Pulsar features, see [Client Feature Matrix](https://pulsar.apache.org/client-feature-matrix/).
|
|
25
25
|
|
|
26
26
|
This library works only in Node.js 10.x or later because it uses the
|
|
27
27
|
[node-addon-api](https://github.com/nodejs/node-addon-api) module to wrap the C++ library.
|
|
@@ -73,7 +73,7 @@ const Pulsar = require('pulsar-client');
|
|
|
73
73
|
data: Buffer.from("hello")
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
// Receive the message
|
|
76
|
+
// Receive the message
|
|
77
77
|
const msg = await consumer.receive();
|
|
78
78
|
console.log(msg.getData().toString());
|
|
79
79
|
consumer.acknowledge(msg);
|
|
@@ -96,7 +96,7 @@ You can see more examples in the [examples](./examples) directory. However, sinc
|
|
|
96
96
|
|
|
97
97
|
> **Note**
|
|
98
98
|
>
|
|
99
|
-
>
|
|
99
|
+
> Building from source code requires a Node.js version greater than 16.18.
|
|
100
100
|
|
|
101
101
|
First, clone the repository.
|
|
102
102
|
|
|
@@ -134,6 +134,10 @@ npm install
|
|
|
134
134
|
|
|
135
135
|
To verify it has been installed successfully, you can run an example like:
|
|
136
136
|
|
|
137
|
+
> **Note**
|
|
138
|
+
>
|
|
139
|
+
> A running Pulsar server is required. The example uses `pulsar://localhost:6650` to connect to the server.
|
|
140
|
+
|
|
137
141
|
```shell
|
|
138
142
|
node examples/producer
|
|
139
143
|
```
|
|
@@ -155,9 +159,15 @@ Sent message: my-message-9
|
|
|
155
159
|
|
|
156
160
|
## Documentation
|
|
157
161
|
|
|
158
|
-
|
|
162
|
+
For more details about Pulsar Node.js clients, see [Pulsar docs](https://pulsar.apache.org/docs/client-libraries-node/).
|
|
163
|
+
|
|
164
|
+
### Contribute
|
|
165
|
+
|
|
166
|
+
Contributions are welcomed and greatly appreciated.
|
|
167
|
+
|
|
168
|
+
If your contribution adds Pulsar features for Node.js clients, you need to update both the [Pulsar docs](https://pulsar.apache.org/docs/client-libraries/) and the [Client Feature Matrix](https://pulsar.apache.org/client-feature-matrix/). See [Contribution Guide](https://pulsar.apache.org/contribute/site-intro/#pages) for more details.
|
|
159
169
|
|
|
160
|
-
|
|
170
|
+
### Generate API docs
|
|
161
171
|
|
|
162
172
|
```shell
|
|
163
173
|
npm install
|
package/binding.gyp
CHANGED
|
@@ -57,11 +57,25 @@
|
|
|
57
57
|
"dependencies": [
|
|
58
58
|
"<!@(node -p \"require('node-addon-api').gyp\")"
|
|
59
59
|
],
|
|
60
|
-
"
|
|
61
|
-
"pkg/mac/build-pulsar/install
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
"conditions": [
|
|
61
|
+
['"<!(test -e <(module_root_dir)/pkg/mac/build-pulsar/install && echo true || echo false)"=="true"', {
|
|
62
|
+
"include_dirs": [
|
|
63
|
+
"<(module_root_dir)/pkg/mac/build-pulsar/install/include"
|
|
64
|
+
],
|
|
65
|
+
"libraries": [
|
|
66
|
+
"<(module_root_dir)/pkg/mac/build-pulsar/install/lib/libpulsarwithdeps.a"
|
|
67
|
+
]
|
|
68
|
+
}, {
|
|
69
|
+
"include_dirs": [
|
|
70
|
+
"<!(brew --prefix)/include"
|
|
71
|
+
],
|
|
72
|
+
"library_dirs": [
|
|
73
|
+
"<!(brew --prefix)/lib"
|
|
74
|
+
],
|
|
75
|
+
"libraries": [
|
|
76
|
+
"-lpulsar"
|
|
77
|
+
]
|
|
78
|
+
}]
|
|
65
79
|
],
|
|
66
80
|
}],
|
|
67
81
|
['OS=="win"', {
|
|
@@ -75,10 +89,10 @@
|
|
|
75
89
|
},
|
|
76
90
|
},
|
|
77
91
|
"include_dirs": [
|
|
78
|
-
"pkg\\windows\\pulsar-cpp\\include"
|
|
92
|
+
"<(module_root_dir)\\pkg\\windows\\pulsar-cpp\\include"
|
|
79
93
|
],
|
|
80
94
|
"libraries": [
|
|
81
|
-
"
|
|
95
|
+
"<(module_root_dir)\\pkg\\windows\\pulsar-cpp\\lib\\pulsarWithDeps.lib"
|
|
82
96
|
],
|
|
83
97
|
"dependencies": [
|
|
84
98
|
"<!(node -p \"require('node-addon-api').gyp\")"
|
|
@@ -91,11 +105,19 @@
|
|
|
91
105
|
"dependencies": [
|
|
92
106
|
"<!@(node -p \"require('node-addon-api').gyp\")"
|
|
93
107
|
],
|
|
94
|
-
"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
"conditions": [
|
|
109
|
+
['"<!(test -e <(module_root_dir)/pkg/linux/pulsar-cpp && echo true || echo false)"=="true"', {
|
|
110
|
+
"include_dirs": [
|
|
111
|
+
"<(module_root_dir)/pkg/linux/pulsar-cpp/include"
|
|
112
|
+
],
|
|
113
|
+
"libraries": [
|
|
114
|
+
"<(module_root_dir)/pkg/linux/pulsar-cpp/lib/libpulsarwithdeps.a"
|
|
115
|
+
]
|
|
116
|
+
}, {
|
|
117
|
+
"libraries": [
|
|
118
|
+
"-lpulsar"
|
|
119
|
+
]
|
|
120
|
+
}]
|
|
99
121
|
],
|
|
100
122
|
}]
|
|
101
123
|
]
|
|
@@ -25,7 +25,7 @@ cd $SRC_DIR
|
|
|
25
25
|
|
|
26
26
|
build-support/pulsar-test-service-stop.sh
|
|
27
27
|
|
|
28
|
-
CONTAINER_ID=$(docker run -i -p 8080:8080 -p 6650:6650 -p 8443:8443 -p 6651:6651 --rm --detach apachepulsar/pulsar:
|
|
28
|
+
CONTAINER_ID=$(docker run -i -p 8080:8080 -p 6650:6650 -p 8443:8443 -p 6651:6651 --rm --detach apachepulsar/pulsar:latest sleep 3600)
|
|
29
29
|
|
|
30
30
|
echo $CONTAINER_ID >.tests-container-id.txt
|
|
31
31
|
|
package/docs/release-process.md
CHANGED
|
@@ -34,6 +34,17 @@ The steps for releasing are as follows:
|
|
|
34
34
|
9. Add release notes
|
|
35
35
|
10. Announce the release
|
|
36
36
|
|
|
37
|
+
## Versioning
|
|
38
|
+
Bump up the version number as follows.
|
|
39
|
+
|
|
40
|
+
* Major version (e.g. 1.8.0 => 2.0.0)
|
|
41
|
+
* Changes that break backward compatibility
|
|
42
|
+
* Minor version (e.g. 1.8.0 => 1.9.0)
|
|
43
|
+
* Backward compatible new features
|
|
44
|
+
* Patch version (e.g. 1.8.0 => 1.8.1)
|
|
45
|
+
* Backward compatible bug fixes
|
|
46
|
+
* C++ Client upgrade (even though there are no new commits in the Nodejs client)
|
|
47
|
+
|
|
37
48
|
## Requirements
|
|
38
49
|
If you haven't already done it, [create and publish the GPG key](https://pulsar.apache.org/contribute/releasing/create-gpg-keys) to sign the release artifacts.
|
|
39
50
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const Pulsar = require('../');
|
|
21
|
+
|
|
22
|
+
(async () => {
|
|
23
|
+
// Create a client
|
|
24
|
+
const client = new Pulsar.Client({
|
|
25
|
+
serviceUrl: 'pulsar://localhost:6650',
|
|
26
|
+
operationTimeoutSeconds: 30
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Create a reader
|
|
30
|
+
const reader = await client.createReader({
|
|
31
|
+
topic: 'persistent://public/default/my-topic',
|
|
32
|
+
startMessageId: Pulsar.MessageId.earliest(),
|
|
33
|
+
privateKeyPath: './certificate/private-key.client-rsa.pem'
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Receive messages
|
|
37
|
+
for (let i = 0; i < 10; i += 1) {
|
|
38
|
+
const msg = await reader.readNext();
|
|
39
|
+
console.log(msg.getData().toString());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await reader.close();
|
|
43
|
+
await client.close();
|
|
44
|
+
})();
|
package/index.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export class Client {
|
|
|
39
39
|
createProducer(config: ProducerConfig): Promise<Producer>;
|
|
40
40
|
subscribe(config: ConsumerConfig): Promise<Consumer>;
|
|
41
41
|
createReader(config: ReaderConfig): Promise<Reader>;
|
|
42
|
+
getPartitionsForTopic(topic: string): Promise<string[]>;
|
|
42
43
|
close(): Promise<null>;
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -61,6 +62,8 @@ export interface ProducerConfig {
|
|
|
61
62
|
encryptionKey?: string;
|
|
62
63
|
cryptoFailureAction?: ProducerCryptoFailureAction;
|
|
63
64
|
chunkingEnabled?: boolean;
|
|
65
|
+
schema?: SchemaInfo;
|
|
66
|
+
accessMode?: ProducerAccessMode;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
export class Producer {
|
|
@@ -91,6 +94,10 @@ export interface ConsumerConfig {
|
|
|
91
94
|
cryptoFailureAction?: ConsumerCryptoFailureAction;
|
|
92
95
|
maxPendingChunkedMessage?: number;
|
|
93
96
|
autoAckOldestChunkedMessageOnQueueFull?: number;
|
|
97
|
+
schema?: SchemaInfo;
|
|
98
|
+
batchIndexAckEnabled?: boolean;
|
|
99
|
+
regexSubscriptionMode?: RegexSubscriptionMode;
|
|
100
|
+
deadLetterPolicy?: DeadLetterPolicy;
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
export class Consumer {
|
|
@@ -116,6 +123,8 @@ export interface ReaderConfig {
|
|
|
116
123
|
subscriptionRolePrefix?: string;
|
|
117
124
|
readCompacted?: boolean;
|
|
118
125
|
listener?: (message: Message, reader: Reader) => void;
|
|
126
|
+
privateKeyPath?: string;
|
|
127
|
+
cryptoFailureAction?: ConsumerCryptoFailureAction;
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
export class Reader {
|
|
@@ -159,6 +168,19 @@ export class MessageId {
|
|
|
159
168
|
toString(): string;
|
|
160
169
|
}
|
|
161
170
|
|
|
171
|
+
export interface SchemaInfo {
|
|
172
|
+
schemaType: SchemaType;
|
|
173
|
+
name?: string;
|
|
174
|
+
schema?: string;
|
|
175
|
+
properties?: Record<string, string>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface DeadLetterPolicy {
|
|
179
|
+
deadLetterTopic: string;
|
|
180
|
+
maxRedeliverCount?: number;
|
|
181
|
+
initialSubscriptionName?: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
162
184
|
export class AuthenticationTls {
|
|
163
185
|
constructor(params: { certificatePath: string, privateKeyPath: string });
|
|
164
186
|
}
|
|
@@ -236,3 +258,32 @@ export type ConsumerCryptoFailureAction =
|
|
|
236
258
|
'FAIL' |
|
|
237
259
|
'DISCARD' |
|
|
238
260
|
'CONSUME';
|
|
261
|
+
|
|
262
|
+
export type RegexSubscriptionMode =
|
|
263
|
+
'PersistentOnly' |
|
|
264
|
+
'NonPersistentOnly' |
|
|
265
|
+
'AllTopics';
|
|
266
|
+
|
|
267
|
+
export type SchemaType =
|
|
268
|
+
'None' |
|
|
269
|
+
'String' |
|
|
270
|
+
'Json' |
|
|
271
|
+
'Protobuf' |
|
|
272
|
+
'Avro' |
|
|
273
|
+
'Boolean' |
|
|
274
|
+
'Int8' |
|
|
275
|
+
'Int16' |
|
|
276
|
+
'Int32' |
|
|
277
|
+
'Int64' |
|
|
278
|
+
'Float32' |
|
|
279
|
+
'Float64' |
|
|
280
|
+
'KeyValue' |
|
|
281
|
+
'Bytes' |
|
|
282
|
+
'AutoConsume' |
|
|
283
|
+
'AutoPublish';
|
|
284
|
+
|
|
285
|
+
export type ProducerAccessMode =
|
|
286
|
+
'Shared' |
|
|
287
|
+
'Exclusive' |
|
|
288
|
+
'WaitForExclusive' |
|
|
289
|
+
'ExclusiveWithFencing';
|
package/package.json
CHANGED
package/perf/perf_producer.js
CHANGED
|
@@ -77,7 +77,7 @@ const Pulsar = require('../index.js');
|
|
|
77
77
|
const numOfMessages = commander.messages;
|
|
78
78
|
for (let i = 0; i < commander.iteration; i += 1) {
|
|
79
79
|
const histogram = hdr.build({
|
|
80
|
-
bitBucketSize:
|
|
80
|
+
bitBucketSize: 64,
|
|
81
81
|
highestTrackableValue: 120000 * 1000,
|
|
82
82
|
numberOfSignificantValueDigits: 5,
|
|
83
83
|
});
|
|
@@ -104,7 +104,7 @@ const Pulsar = require('../index.js');
|
|
|
104
104
|
console.log('Throughput produced: %f msg/s --- %f Mbit/s --- Latency: mean: %f ms - med: %f - 95pct: %f - 99pct: %f - 99.9pct: %f - 99.99pct: %f - Max: %f',
|
|
105
105
|
rate.toFixed(3),
|
|
106
106
|
throuhputMbit.toFixed(3),
|
|
107
|
-
histogram.
|
|
107
|
+
histogram.mean,
|
|
108
108
|
histogram.getValueAtPercentile(50),
|
|
109
109
|
histogram.getValueAtPercentile(95),
|
|
110
110
|
histogram.getValueAtPercentile(99),
|
package/pulsar-client-cpp.txt
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
CPP_CLIENT_BASE_URL=https://archive.apache.org/dist/pulsar/pulsar-client-cpp-3.
|
|
2
|
-
CPP_CLIENT_VERSION=3.
|
|
1
|
+
CPP_CLIENT_BASE_URL=https://archive.apache.org/dist/pulsar/pulsar-client-cpp-3.2.0
|
|
2
|
+
CPP_CLIENT_VERSION=3.2.0
|
package/src/Client.cc
CHANGED
|
@@ -79,6 +79,7 @@ Napi::Object Client::Init(Napi::Env env, Napi::Object exports) {
|
|
|
79
79
|
{StaticMethod("setLogHandler", &Client::SetLogHandler),
|
|
80
80
|
InstanceMethod("createProducer", &Client::CreateProducer),
|
|
81
81
|
InstanceMethod("subscribe", &Client::Subscribe), InstanceMethod("createReader", &Client::CreateReader),
|
|
82
|
+
InstanceMethod("getPartitionsForTopic", &Client::GetPartitionsForTopic),
|
|
82
83
|
InstanceMethod("close", &Client::Close)});
|
|
83
84
|
|
|
84
85
|
constructor = Napi::Persistent(func);
|
|
@@ -206,6 +207,41 @@ Napi::Value Client::CreateReader(const Napi::CallbackInfo &info) {
|
|
|
206
207
|
return Reader::NewInstance(info, this->cClient);
|
|
207
208
|
}
|
|
208
209
|
|
|
210
|
+
Napi::Value Client::GetPartitionsForTopic(const Napi::CallbackInfo &info) {
|
|
211
|
+
Napi::String topicString = info[0].As<Napi::String>();
|
|
212
|
+
std::string topic = topicString.Utf8Value();
|
|
213
|
+
auto deferred = ThreadSafeDeferred::New(Env());
|
|
214
|
+
auto ctx = new ExtDeferredContext(deferred);
|
|
215
|
+
|
|
216
|
+
pulsar_client_get_topic_partitions_async(
|
|
217
|
+
this->cClient.get(), topic.c_str(),
|
|
218
|
+
[](pulsar_result result, pulsar_string_list_t *topicList, void *ctx) {
|
|
219
|
+
auto deferredContext = static_cast<ExtDeferredContext *>(ctx);
|
|
220
|
+
auto deferred = deferredContext->deferred;
|
|
221
|
+
delete deferredContext;
|
|
222
|
+
|
|
223
|
+
if (result == pulsar_result_Ok && topicList != nullptr) {
|
|
224
|
+
deferred->Resolve([topicList](const Napi::Env env) {
|
|
225
|
+
int listSize = pulsar_string_list_size(topicList);
|
|
226
|
+
Napi::Array jsArray = Napi::Array::New(env, listSize);
|
|
227
|
+
|
|
228
|
+
for (int i = 0; i < listSize; i++) {
|
|
229
|
+
const char *str = pulsar_string_list_get(topicList, i);
|
|
230
|
+
jsArray.Set(i, Napi::String::New(env, str));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return jsArray;
|
|
234
|
+
});
|
|
235
|
+
} else {
|
|
236
|
+
deferred->Reject(std::string("Failed to GetPartitionsForTopic: ") + pulsar_result_str(result));
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
ctx);
|
|
241
|
+
|
|
242
|
+
return deferred->Promise();
|
|
243
|
+
}
|
|
244
|
+
|
|
209
245
|
void LogMessageProxy(Napi::Env env, Napi::Function jsCallback, struct LogMessage *logMessage) {
|
|
210
246
|
Napi::Number logLevel = Napi::Number::New(env, static_cast<double>(logMessage->level));
|
|
211
247
|
Napi::String file = Napi::String::New(env, logMessage->file);
|
package/src/Client.h
CHANGED
|
@@ -42,6 +42,9 @@ class Client : public Napi::ObjectWrap<Client> {
|
|
|
42
42
|
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
43
43
|
static void SetLogHandler(const Napi::CallbackInfo &info);
|
|
44
44
|
|
|
45
|
+
static void LogMessage(pulsar_logger_level_t level, const char *file, int line, const char *message,
|
|
46
|
+
void *ctx);
|
|
47
|
+
|
|
45
48
|
Client(const Napi::CallbackInfo &info);
|
|
46
49
|
~Client();
|
|
47
50
|
|
|
@@ -51,11 +54,10 @@ class Client : public Napi::ObjectWrap<Client> {
|
|
|
51
54
|
std::shared_ptr<pulsar_client_t> cClient;
|
|
52
55
|
std::shared_ptr<pulsar_client_configuration_t> cClientConfig;
|
|
53
56
|
|
|
54
|
-
static void LogMessage(pulsar_logger_level_t level, const char *file, int line, const char *message,
|
|
55
|
-
void *ctx);
|
|
56
57
|
Napi::Value CreateProducer(const Napi::CallbackInfo &info);
|
|
57
58
|
Napi::Value Subscribe(const Napi::CallbackInfo &info);
|
|
58
59
|
Napi::Value CreateReader(const Napi::CallbackInfo &info);
|
|
60
|
+
Napi::Value GetPartitionsForTopic(const Napi::CallbackInfo &info);
|
|
59
61
|
Napi::Value Close(const Napi::CallbackInfo &info);
|
|
60
62
|
};
|
|
61
63
|
|
package/src/Client.js
CHANGED
package/src/Consumer.cc
CHANGED
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
#include "Message.h"
|
|
23
23
|
#include "MessageId.h"
|
|
24
24
|
#include "ThreadSafeDeferred.h"
|
|
25
|
+
#include "LogUtils.h"
|
|
25
26
|
#include <pulsar/c/result.h>
|
|
26
27
|
#include <atomic>
|
|
27
28
|
#include <thread>
|
|
28
29
|
#include <future>
|
|
30
|
+
#include <sstream>
|
|
29
31
|
|
|
30
32
|
Napi::FunctionReference Consumer::constructor;
|
|
31
33
|
|
|
@@ -63,6 +65,13 @@ struct MessageListenerProxyData {
|
|
|
63
65
|
: cMessage(cMessage), consumer(consumer), callback(callback) {}
|
|
64
66
|
};
|
|
65
67
|
|
|
68
|
+
inline void logMessageListenerError(Consumer *consumer, const char *err) {
|
|
69
|
+
std::ostringstream ss;
|
|
70
|
+
ss << "[" << consumer->GetTopic() << "][" << consumer->GetSubscriptionName()
|
|
71
|
+
<< "] Message listener error in processing message: " << err;
|
|
72
|
+
LOG_ERROR(ss.str().c_str());
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
void MessageListenerProxy(Napi::Env env, Napi::Function jsCallback, MessageListenerProxyData *data) {
|
|
67
76
|
Napi::Object msg = Message::NewInstance({}, data->cMessage);
|
|
68
77
|
Consumer *consumer = data->consumer;
|
|
@@ -70,17 +79,28 @@ void MessageListenerProxy(Napi::Env env, Napi::Function jsCallback, MessageListe
|
|
|
70
79
|
// `consumer` might be null in certain cases, segmentation fault might happend without this null check. We
|
|
71
80
|
// need to handle this rare case in future.
|
|
72
81
|
if (consumer) {
|
|
73
|
-
Napi::Value ret
|
|
82
|
+
Napi::Value ret;
|
|
83
|
+
try {
|
|
84
|
+
ret = jsCallback.Call({msg, consumer->Value()});
|
|
85
|
+
} catch (std::exception &exception) {
|
|
86
|
+
logMessageListenerError(consumer, exception.what());
|
|
87
|
+
}
|
|
88
|
+
|
|
74
89
|
if (ret.IsPromise()) {
|
|
75
90
|
Napi::Promise promise = ret.As<Napi::Promise>();
|
|
76
|
-
Napi::
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
Napi::Function catchFunc = promise.Get("catch").As<Napi::Function>();
|
|
92
|
+
|
|
93
|
+
ret = catchFunc.Call(promise, {Napi::Function::New(env, [consumer](const Napi::CallbackInfo &info) {
|
|
94
|
+
Napi::Error error = info[0].As<Napi::Error>();
|
|
95
|
+
logMessageListenerError(consumer, error.what());
|
|
96
|
+
})});
|
|
97
|
+
|
|
98
|
+
promise = ret.As<Napi::Promise>();
|
|
99
|
+
Napi::Function finallyFunc = promise.Get("finally").As<Napi::Function>();
|
|
100
|
+
|
|
101
|
+
finallyFunc.Call(
|
|
102
|
+
promise, {Napi::Function::New(env, [data](const Napi::CallbackInfo &info) { data->callback(); })});
|
|
103
|
+
return;
|
|
84
104
|
}
|
|
85
105
|
}
|
|
86
106
|
data->callback();
|
|
@@ -227,6 +247,12 @@ Napi::Value Consumer::NewInstance(const Napi::CallbackInfo &info, std::shared_pt
|
|
|
227
247
|
return deferred->Promise();
|
|
228
248
|
}
|
|
229
249
|
|
|
250
|
+
std::string Consumer::GetTopic() { return {pulsar_consumer_get_topic(this->cConsumer.get())}; }
|
|
251
|
+
|
|
252
|
+
std::string Consumer::GetSubscriptionName() {
|
|
253
|
+
return {pulsar_consumer_get_subscription_name(this->cConsumer.get())};
|
|
254
|
+
}
|
|
255
|
+
|
|
230
256
|
// We still need a receive worker because the c api is missing the equivalent async definition
|
|
231
257
|
class ConsumerReceiveWorker : public Napi::AsyncWorker {
|
|
232
258
|
public:
|
package/src/Consumer.h
CHANGED
|
@@ -36,6 +36,8 @@ class Consumer : public Napi::ObjectWrap<Consumer> {
|
|
|
36
36
|
void SetListenerCallback(MessageListenerCallback *listener);
|
|
37
37
|
void Cleanup();
|
|
38
38
|
void CleanupListener();
|
|
39
|
+
std::string GetTopic();
|
|
40
|
+
std::string GetSubscriptionName();
|
|
39
41
|
|
|
40
42
|
private:
|
|
41
43
|
std::shared_ptr<pulsar_consumer_t> cConsumer;
|
package/src/ConsumerConfig.cc
CHANGED
|
@@ -31,6 +31,7 @@ static const std::string CFG_TOPICS_PATTERN = "topicsPattern";
|
|
|
31
31
|
static const std::string CFG_SUBSCRIPTION = "subscription";
|
|
32
32
|
static const std::string CFG_SUBSCRIPTION_TYPE = "subscriptionType";
|
|
33
33
|
static const std::string CFG_INIT_POSITION = "subscriptionInitialPosition";
|
|
34
|
+
static const std::string CFG_REGEX_SUBSCRIPTION_MODE = "regexSubscriptionMode";
|
|
34
35
|
static const std::string CFG_ACK_TIMEOUT = "ackTimeoutMs";
|
|
35
36
|
static const std::string CFG_NACK_REDELIVER_TIMEOUT = "nAckRedeliverTimeoutMs";
|
|
36
37
|
static const std::string CFG_RECV_QUEUE = "receiverQueueSize";
|
|
@@ -45,6 +46,11 @@ static const std::string CFG_CRYPTO_FAILURE_ACTION = "cryptoFailureAction";
|
|
|
45
46
|
static const std::string CFG_MAX_PENDING_CHUNKED_MESSAGE = "maxPendingChunkedMessage";
|
|
46
47
|
static const std::string CFG_AUTO_ACK_OLDEST_CHUNKED_MESSAGE_ON_QUEUE_FULL =
|
|
47
48
|
"autoAckOldestChunkedMessageOnQueueFull";
|
|
49
|
+
static const std::string CFG_BATCH_INDEX_ACK_ENABLED = "batchIndexAckEnabled";
|
|
50
|
+
static const std::string CFG_DEAD_LETTER_POLICY = "deadLetterPolicy";
|
|
51
|
+
static const std::string CFG_DLQ_POLICY_TOPIC = "deadLetterTopic";
|
|
52
|
+
static const std::string CFG_DLQ_POLICY_MAX_REDELIVER_COUNT = "maxRedeliverCount";
|
|
53
|
+
static const std::string CFG_DLQ_POLICY_INIT_SUB_NAME = "initialSubscriptionName";
|
|
48
54
|
|
|
49
55
|
static const std::map<std::string, pulsar_consumer_type> SUBSCRIPTION_TYPE = {
|
|
50
56
|
{"Exclusive", pulsar_ConsumerExclusive},
|
|
@@ -52,6 +58,11 @@ static const std::map<std::string, pulsar_consumer_type> SUBSCRIPTION_TYPE = {
|
|
|
52
58
|
{"KeyShared", pulsar_ConsumerKeyShared},
|
|
53
59
|
{"Failover", pulsar_ConsumerFailover}};
|
|
54
60
|
|
|
61
|
+
static const std::map<std::string, pulsar_consumer_regex_subscription_mode> REGEX_SUBSCRIPTION_MODE = {
|
|
62
|
+
{"PersistentOnly", pulsar_consumer_regex_sub_mode_PersistentOnly},
|
|
63
|
+
{"NonPersistentOnly", pulsar_consumer_regex_sub_mode_NonPersistentOnly},
|
|
64
|
+
{"AllTopics", pulsar_consumer_regex_sub_mode_AllTopics}};
|
|
65
|
+
|
|
55
66
|
static const std::map<std::string, initial_position> INIT_POSITION = {
|
|
56
67
|
{"Latest", initial_position_latest}, {"Earliest", initial_position_earliest}};
|
|
57
68
|
|
|
@@ -110,6 +121,16 @@ ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig, pulsar_messag
|
|
|
110
121
|
}
|
|
111
122
|
}
|
|
112
123
|
|
|
124
|
+
if (consumerConfig.Has(CFG_REGEX_SUBSCRIPTION_MODE) &&
|
|
125
|
+
consumerConfig.Get(CFG_REGEX_SUBSCRIPTION_MODE).IsString()) {
|
|
126
|
+
std::string regexSubscriptionMode =
|
|
127
|
+
consumerConfig.Get(CFG_REGEX_SUBSCRIPTION_MODE).ToString().Utf8Value();
|
|
128
|
+
if (REGEX_SUBSCRIPTION_MODE.count(regexSubscriptionMode)) {
|
|
129
|
+
pulsar_consumer_configuration_set_regex_subscription_mode(
|
|
130
|
+
this->cConsumerConfig.get(), REGEX_SUBSCRIPTION_MODE.at(regexSubscriptionMode));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
113
134
|
if (consumerConfig.Has(CFG_CONSUMER_NAME) && consumerConfig.Get(CFG_CONSUMER_NAME).IsString()) {
|
|
114
135
|
std::string consumerName = consumerConfig.Get(CFG_CONSUMER_NAME).ToString().Utf8Value();
|
|
115
136
|
if (!consumerName.empty())
|
|
@@ -215,6 +236,35 @@ ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig, pulsar_messag
|
|
|
215
236
|
pulsar_consumer_configuration_set_auto_ack_oldest_chunked_message_on_queue_full(
|
|
216
237
|
this->cConsumerConfig.get(), autoAckOldestChunkedMessageOnQueueFull);
|
|
217
238
|
}
|
|
239
|
+
|
|
240
|
+
if (consumerConfig.Has(CFG_BATCH_INDEX_ACK_ENABLED) &&
|
|
241
|
+
consumerConfig.Get(CFG_BATCH_INDEX_ACK_ENABLED).IsBoolean()) {
|
|
242
|
+
bool batchIndexAckEnabled = consumerConfig.Get(CFG_BATCH_INDEX_ACK_ENABLED).ToBoolean();
|
|
243
|
+
pulsar_consumer_configuration_set_batch_index_ack_enabled(this->cConsumerConfig.get(),
|
|
244
|
+
batchIndexAckEnabled);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (consumerConfig.Has(CFG_DEAD_LETTER_POLICY) && consumerConfig.Get(CFG_DEAD_LETTER_POLICY).IsObject()) {
|
|
248
|
+
pulsar_consumer_config_dead_letter_policy_t dlq_policy{};
|
|
249
|
+
Napi::Object dlqPolicyObject = consumerConfig.Get(CFG_DEAD_LETTER_POLICY).ToObject();
|
|
250
|
+
std::string dlq_topic_str;
|
|
251
|
+
std::string init_subscription_name;
|
|
252
|
+
if (dlqPolicyObject.Has(CFG_DLQ_POLICY_TOPIC) && dlqPolicyObject.Get(CFG_DLQ_POLICY_TOPIC).IsString()) {
|
|
253
|
+
dlq_topic_str = dlqPolicyObject.Get(CFG_DLQ_POLICY_TOPIC).ToString().Utf8Value();
|
|
254
|
+
dlq_policy.dead_letter_topic = dlq_topic_str.c_str();
|
|
255
|
+
}
|
|
256
|
+
if (dlqPolicyObject.Has(CFG_DLQ_POLICY_MAX_REDELIVER_COUNT) &&
|
|
257
|
+
dlqPolicyObject.Get(CFG_DLQ_POLICY_MAX_REDELIVER_COUNT).IsNumber()) {
|
|
258
|
+
dlq_policy.max_redeliver_count =
|
|
259
|
+
dlqPolicyObject.Get(CFG_DLQ_POLICY_MAX_REDELIVER_COUNT).ToNumber().Int32Value();
|
|
260
|
+
}
|
|
261
|
+
if (dlqPolicyObject.Has(CFG_DLQ_POLICY_INIT_SUB_NAME) &&
|
|
262
|
+
dlqPolicyObject.Get(CFG_DLQ_POLICY_INIT_SUB_NAME).IsString()) {
|
|
263
|
+
init_subscription_name = dlqPolicyObject.Get(CFG_DLQ_POLICY_INIT_SUB_NAME).ToString().Utf8Value();
|
|
264
|
+
dlq_policy.initial_subscription_name = init_subscription_name.c_str();
|
|
265
|
+
}
|
|
266
|
+
pulsar_consumer_configuration_set_dlq_policy(this->cConsumerConfig.get(), &dlq_policy);
|
|
267
|
+
}
|
|
218
268
|
}
|
|
219
269
|
|
|
220
270
|
ConsumerConfig::~ConsumerConfig() {
|