kafka-ts 0.0.2-beta → 0.0.2
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/.prettierrc +3 -2
- package/README.md +109 -39
- package/dist/api/api-versions.d.ts +9 -0
- package/dist/api/api-versions.js +24 -0
- package/dist/api/create-topics.d.ts +38 -0
- package/dist/api/create-topics.js +53 -0
- package/dist/api/delete-topics.d.ts +18 -0
- package/dist/api/delete-topics.js +33 -0
- package/dist/api/fetch.d.ts +84 -0
- package/dist/api/fetch.js +142 -0
- package/dist/api/find-coordinator.d.ts +21 -0
- package/dist/api/find-coordinator.js +39 -0
- package/dist/api/heartbeat.d.ts +11 -0
- package/dist/api/heartbeat.js +27 -0
- package/dist/api/index.d.ts +578 -0
- package/dist/api/index.js +165 -0
- package/dist/api/init-producer-id.d.ts +13 -0
- package/dist/api/init-producer-id.js +29 -0
- package/dist/api/join-group.d.ts +34 -0
- package/dist/api/join-group.js +51 -0
- package/dist/api/leave-group.d.ts +19 -0
- package/dist/api/leave-group.js +39 -0
- package/dist/api/list-offsets.d.ts +29 -0
- package/dist/api/list-offsets.js +48 -0
- package/dist/api/metadata.d.ts +40 -0
- package/dist/api/metadata.js +58 -0
- package/dist/api/offset-commit.d.ts +28 -0
- package/dist/api/offset-commit.js +48 -0
- package/dist/api/offset-fetch.d.ts +33 -0
- package/dist/api/offset-fetch.js +57 -0
- package/dist/api/produce.d.ts +54 -0
- package/dist/api/produce.js +126 -0
- package/dist/api/sasl-authenticate.d.ts +11 -0
- package/dist/api/sasl-authenticate.js +23 -0
- package/dist/api/sasl-handshake.d.ts +6 -0
- package/dist/api/sasl-handshake.js +19 -0
- package/dist/api/sync-group.d.ts +24 -0
- package/dist/api/sync-group.js +36 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/plain.d.ts +5 -0
- package/dist/auth/plain.js +12 -0
- package/dist/auth/scram.d.ts +9 -0
- package/dist/auth/scram.js +40 -0
- package/dist/broker.d.ts +30 -0
- package/dist/broker.js +55 -0
- package/dist/client.d.ts +23 -0
- package/dist/client.js +36 -0
- package/dist/cluster.d.ts +27 -0
- package/dist/cluster.js +70 -0
- package/dist/cluster.test.d.ts +1 -0
- package/dist/cluster.test.js +345 -0
- package/dist/codecs/gzip.d.ts +2 -0
- package/dist/codecs/gzip.js +8 -0
- package/dist/codecs/index.d.ts +2 -0
- package/dist/codecs/index.js +17 -0
- package/dist/codecs/none.d.ts +2 -0
- package/dist/codecs/none.js +7 -0
- package/dist/codecs/types.d.ts +5 -0
- package/dist/codecs/types.js +2 -0
- package/dist/connection.d.ts +26 -0
- package/dist/connection.js +175 -0
- package/dist/consumer/consumer-group.d.ts +41 -0
- package/dist/consumer/consumer-group.js +217 -0
- package/dist/consumer/consumer-metadata.d.ts +7 -0
- package/dist/consumer/consumer-metadata.js +14 -0
- package/dist/consumer/consumer.d.ts +44 -0
- package/dist/consumer/consumer.js +225 -0
- package/dist/consumer/fetch-manager.d.ts +33 -0
- package/dist/consumer/fetch-manager.js +140 -0
- package/dist/consumer/fetcher.d.ts +25 -0
- package/dist/consumer/fetcher.js +64 -0
- package/dist/consumer/offset-manager.d.ts +22 -0
- package/dist/consumer/offset-manager.js +66 -0
- package/dist/consumer/processor.d.ts +19 -0
- package/dist/consumer/processor.js +59 -0
- package/dist/distributors/assignments-to-replicas.d.ts +16 -0
- package/dist/distributors/assignments-to-replicas.js +59 -0
- package/dist/distributors/assignments-to-replicas.test.d.ts +1 -0
- package/dist/distributors/assignments-to-replicas.test.js +40 -0
- package/dist/distributors/messages-to-topic-partition-leaders.d.ts +17 -0
- package/dist/distributors/messages-to-topic-partition-leaders.js +15 -0
- package/dist/distributors/messages-to-topic-partition-leaders.test.d.ts +1 -0
- package/dist/distributors/messages-to-topic-partition-leaders.test.js +30 -0
- package/dist/distributors/partitioner.d.ts +7 -0
- package/dist/distributors/partitioner.js +23 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +26 -0
- package/dist/metadata.d.ts +24 -0
- package/dist/metadata.js +106 -0
- package/dist/producer/producer.d.ts +24 -0
- package/dist/producer/producer.js +131 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.js +2 -0
- package/dist/utils/api.d.ts +9 -0
- package/dist/utils/api.js +5 -0
- package/dist/utils/crypto.d.ts +8 -0
- package/dist/utils/crypto.js +18 -0
- package/dist/utils/decoder.d.ts +30 -0
- package/dist/utils/decoder.js +152 -0
- package/dist/utils/delay.d.ts +1 -0
- package/dist/utils/delay.js +5 -0
- package/dist/utils/encoder.d.ts +28 -0
- package/dist/utils/encoder.js +125 -0
- package/dist/utils/error.d.ts +11 -0
- package/dist/utils/error.js +27 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/memo.d.ts +1 -0
- package/dist/utils/memo.js +16 -0
- package/dist/utils/murmur2.d.ts +3 -0
- package/dist/utils/murmur2.js +40 -0
- package/dist/utils/retrier.d.ts +10 -0
- package/dist/utils/retrier.js +22 -0
- package/dist/utils/tracer.d.ts +5 -0
- package/dist/utils/tracer.js +39 -0
- package/package.json +30 -19
- package/src/__snapshots__/{request-handler.test.ts.snap → cluster.test.ts.snap} +329 -26
- package/src/api/api-versions.ts +2 -2
- package/src/api/create-topics.ts +2 -2
- package/src/api/delete-topics.ts +2 -2
- package/src/api/fetch.ts +86 -31
- package/src/api/find-coordinator.ts +2 -2
- package/src/api/heartbeat.ts +2 -2
- package/src/api/index.ts +21 -19
- package/src/api/init-producer-id.ts +2 -2
- package/src/api/join-group.ts +3 -3
- package/src/api/leave-group.ts +2 -2
- package/src/api/list-offsets.ts +3 -3
- package/src/api/metadata.ts +3 -3
- package/src/api/offset-commit.ts +2 -2
- package/src/api/offset-fetch.ts +2 -2
- package/src/api/produce.ts +17 -20
- package/src/api/sasl-authenticate.ts +2 -2
- package/src/api/sasl-handshake.ts +2 -2
- package/src/api/sync-group.ts +2 -2
- package/src/auth/index.ts +2 -0
- package/src/auth/plain.ts +10 -0
- package/src/auth/scram.ts +52 -0
- package/src/broker.ts +12 -14
- package/src/client.ts +7 -7
- package/src/cluster.test.ts +78 -74
- package/src/cluster.ts +43 -45
- package/src/codecs/gzip.ts +9 -0
- package/src/codecs/index.ts +16 -0
- package/src/codecs/none.ts +6 -0
- package/src/codecs/types.ts +4 -0
- package/src/connection.ts +49 -33
- package/src/consumer/consumer-group.ts +57 -35
- package/src/consumer/consumer-metadata.ts +2 -2
- package/src/consumer/consumer.ts +115 -92
- package/src/consumer/fetch-manager.ts +169 -0
- package/src/consumer/fetcher.ts +64 -0
- package/src/consumer/offset-manager.ts +24 -13
- package/src/consumer/processor.ts +53 -0
- package/src/distributors/assignments-to-replicas.test.ts +7 -7
- package/src/distributors/assignments-to-replicas.ts +2 -4
- package/src/distributors/messages-to-topic-partition-leaders.test.ts +6 -6
- package/src/distributors/partitioner.ts +27 -0
- package/src/index.ts +9 -3
- package/src/metadata.ts +8 -4
- package/src/producer/producer.ts +30 -20
- package/src/types.ts +5 -3
- package/src/utils/api.ts +5 -5
- package/src/utils/crypto.ts +15 -0
- package/src/utils/decoder.ts +14 -8
- package/src/utils/encoder.ts +34 -27
- package/src/utils/error.ts +3 -3
- package/src/utils/logger.ts +37 -0
- package/src/utils/murmur2.ts +44 -0
- package/src/utils/retrier.ts +1 -1
- package/src/utils/tracer.ts +41 -20
- package/tsconfig.json +16 -16
- package/.github/workflows/release.yml +0 -17
- package/certs/ca.crt +0 -29
- package/certs/ca.key +0 -52
- package/certs/ca.srl +0 -1
- package/certs/kafka.crt +0 -29
- package/certs/kafka.csr +0 -26
- package/certs/kafka.key +0 -52
- package/certs/kafka.keystore.jks +0 -0
- package/certs/kafka.truststore.jks +0 -0
- package/docker-compose.yml +0 -104
- package/examples/package-lock.json +0 -31
- package/examples/package.json +0 -14
- package/examples/src/client.ts +0 -9
- package/examples/src/consumer.ts +0 -17
- package/examples/src/create-topic.ts +0 -37
- package/examples/src/producer.ts +0 -24
- package/examples/src/replicator.ts +0 -25
- package/examples/src/utils/json.ts +0 -1
- package/examples/tsconfig.json +0 -7
- package/log4j.properties +0 -95
- package/scripts/generate-certs.sh +0 -24
- package/src/utils/debug.ts +0 -9
package/src/cluster.test.ts
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
|
-
import { randomBytes } from
|
|
2
|
-
import { readFileSync } from
|
|
3
|
-
import { afterAll, beforeAll, describe, expect, it } from
|
|
4
|
-
import { API } from
|
|
5
|
-
import { KEY_TYPE } from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
4
|
+
import { API } from './api';
|
|
5
|
+
import { KEY_TYPE } from './api/find-coordinator';
|
|
6
|
+
import { saslPlain } from './auth';
|
|
7
|
+
import { createKafkaClient } from './client';
|
|
8
|
+
import { Cluster } from './cluster';
|
|
9
|
+
import { KafkaTSApiError } from './utils/error';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
clientId:
|
|
12
|
-
bootstrapServers: [{ host:
|
|
13
|
-
sasl: {
|
|
14
|
-
ssl: { ca: readFileSync(
|
|
11
|
+
const kafka = createKafkaClient({
|
|
12
|
+
clientId: 'kafka-ts',
|
|
13
|
+
bootstrapServers: [{ host: 'localhost', port: 9092 }],
|
|
14
|
+
sasl: saslPlain({ username: 'admin', password: 'admin' }),
|
|
15
|
+
ssl: { ca: readFileSync('./certs/ca.crt').toString() },
|
|
15
16
|
});
|
|
16
17
|
|
|
17
|
-
describe.sequential(
|
|
18
|
-
const groupId = randomBytes(16).toString(
|
|
18
|
+
describe.sequential('Low-level API', () => {
|
|
19
|
+
const groupId = randomBytes(16).toString('hex');
|
|
19
20
|
|
|
20
21
|
let cluster: Cluster;
|
|
21
22
|
|
|
22
23
|
beforeAll(async () => {
|
|
23
|
-
cluster = await kafka.createCluster()
|
|
24
|
+
cluster = await kafka.createCluster();
|
|
25
|
+
await cluster.connect();
|
|
24
26
|
|
|
25
27
|
const metadataResult = await cluster.sendRequest(API.METADATA, {
|
|
26
28
|
topics: null,
|
|
27
29
|
allowTopicAutoCreation: false,
|
|
28
30
|
includeTopicAuthorizedOperations: false,
|
|
29
31
|
});
|
|
30
|
-
if (metadataResult.topics.some((topic) => topic.name ===
|
|
32
|
+
if (metadataResult.topics.some((topic) => topic.name === 'kafka-ts-test-topic')) {
|
|
31
33
|
await cluster.sendRequest(API.DELETE_TOPICS, {
|
|
32
|
-
topics: [{ name:
|
|
34
|
+
topics: [{ name: 'kafka-ts-test-topic', topicId: null }],
|
|
33
35
|
timeoutMs: 10000,
|
|
34
36
|
});
|
|
35
37
|
}
|
|
@@ -39,20 +41,20 @@ describe.sequential("Request handler", () => {
|
|
|
39
41
|
await cluster.disconnect();
|
|
40
42
|
});
|
|
41
43
|
|
|
42
|
-
it(
|
|
44
|
+
it('should request api versions', async () => {
|
|
43
45
|
const result = await cluster.sendRequest(API.API_VERSIONS, {});
|
|
44
46
|
expect(result).toMatchSnapshot();
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
let topicId: string =
|
|
49
|
+
let topicId: string = 'd6718d178e1b47c886441ad2d19faea5';
|
|
48
50
|
|
|
49
|
-
it(
|
|
51
|
+
it('should create topics', async () => {
|
|
50
52
|
const result = await cluster.sendRequest(API.CREATE_TOPICS, {
|
|
51
53
|
topics: [
|
|
52
54
|
{
|
|
53
|
-
name:
|
|
54
|
-
numPartitions:
|
|
55
|
-
replicationFactor:
|
|
55
|
+
name: 'kafka-ts-test-topic',
|
|
56
|
+
numPartitions: 10,
|
|
57
|
+
replicationFactor: 3,
|
|
56
58
|
assignments: [],
|
|
57
59
|
configs: [],
|
|
58
60
|
},
|
|
@@ -62,23 +64,23 @@ describe.sequential("Request handler", () => {
|
|
|
62
64
|
});
|
|
63
65
|
topicId = result.topics[0].topicId;
|
|
64
66
|
result.topics.forEach((topic) => {
|
|
65
|
-
topic.topicId =
|
|
67
|
+
topic.topicId = 'Any<UUID>';
|
|
66
68
|
});
|
|
67
69
|
expect(result).toMatchSnapshot();
|
|
68
70
|
|
|
69
71
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
70
72
|
});
|
|
71
73
|
|
|
72
|
-
it(
|
|
74
|
+
it('should request metadata for all topics', async () => {
|
|
73
75
|
const result = await cluster.sendRequest(API.METADATA, {
|
|
74
76
|
topics: null,
|
|
75
77
|
allowTopicAutoCreation: false,
|
|
76
78
|
includeTopicAuthorizedOperations: false,
|
|
77
79
|
});
|
|
78
80
|
result.controllerId = 0;
|
|
79
|
-
result.topics = result.topics.filter((topic) => topic.name !==
|
|
81
|
+
result.topics = result.topics.filter((topic) => topic.name !== '__consumer_offsets');
|
|
80
82
|
result.topics.forEach((topic) => {
|
|
81
|
-
topic.topicId =
|
|
83
|
+
topic.topicId = 'Any<UUID>';
|
|
82
84
|
topic.partitions.forEach((partition) => {
|
|
83
85
|
partition.leaderId = 0;
|
|
84
86
|
partition.isrNodes = [0];
|
|
@@ -88,18 +90,20 @@ describe.sequential("Request handler", () => {
|
|
|
88
90
|
expect(result).toMatchSnapshot();
|
|
89
91
|
});
|
|
90
92
|
|
|
93
|
+
let partitionIndex = 0;
|
|
91
94
|
let leaderId = 0;
|
|
92
95
|
|
|
93
|
-
it(
|
|
96
|
+
it('should request metadata for a topic', async () => {
|
|
94
97
|
const result = await cluster.sendRequest(API.METADATA, {
|
|
95
|
-
topics: [{ id: topicId, name:
|
|
98
|
+
topics: [{ id: topicId, name: 'kafka-ts-test-topic' }],
|
|
96
99
|
allowTopicAutoCreation: false,
|
|
97
100
|
includeTopicAuthorizedOperations: false,
|
|
98
101
|
});
|
|
102
|
+
partitionIndex = result.topics[0].partitions[0].partitionIndex;
|
|
99
103
|
leaderId = result.topics[0].partitions[0].leaderId;
|
|
100
104
|
result.controllerId = 0;
|
|
101
105
|
result.topics.forEach((topic) => {
|
|
102
|
-
topic.topicId =
|
|
106
|
+
topic.topicId = 'Any<UUID>';
|
|
103
107
|
topic.partitions.forEach((partition) => {
|
|
104
108
|
partition.leaderId = 0;
|
|
105
109
|
partition.isrNodes = [0];
|
|
@@ -111,7 +115,7 @@ describe.sequential("Request handler", () => {
|
|
|
111
115
|
|
|
112
116
|
let producerId = 9n;
|
|
113
117
|
|
|
114
|
-
it(
|
|
118
|
+
it('should init producer id', async () => {
|
|
115
119
|
const result = await cluster.sendRequest(API.INIT_PRODUCER_ID, {
|
|
116
120
|
transactionalId: null,
|
|
117
121
|
transactionTimeoutMs: 0,
|
|
@@ -122,7 +126,7 @@ describe.sequential("Request handler", () => {
|
|
|
122
126
|
expect(result).toMatchSnapshot();
|
|
123
127
|
});
|
|
124
128
|
|
|
125
|
-
it(
|
|
129
|
+
it('should produce messages', async () => {
|
|
126
130
|
const now = Date.now();
|
|
127
131
|
const result = await cluster.sendRequestToNode(leaderId)(API.PRODUCE, {
|
|
128
132
|
transactionalId: null,
|
|
@@ -130,10 +134,10 @@ describe.sequential("Request handler", () => {
|
|
|
130
134
|
acks: 1,
|
|
131
135
|
topicData: [
|
|
132
136
|
{
|
|
133
|
-
name:
|
|
137
|
+
name: 'kafka-ts-test-topic',
|
|
134
138
|
partitionData: [
|
|
135
139
|
{
|
|
136
|
-
index:
|
|
140
|
+
index: partitionIndex,
|
|
137
141
|
baseOffset: 0n,
|
|
138
142
|
partitionLeaderEpoch: 0,
|
|
139
143
|
attributes: 0,
|
|
@@ -148,12 +152,12 @@ describe.sequential("Request handler", () => {
|
|
|
148
152
|
attributes: 0,
|
|
149
153
|
offsetDelta: 0,
|
|
150
154
|
timestampDelta: 0n,
|
|
151
|
-
key:
|
|
152
|
-
value:
|
|
155
|
+
key: Buffer.from('key'),
|
|
156
|
+
value: Buffer.from('value'),
|
|
153
157
|
headers: [
|
|
154
158
|
{
|
|
155
|
-
key:
|
|
156
|
-
value:
|
|
159
|
+
key: Buffer.from('header-key'),
|
|
160
|
+
value: Buffer.from('header-value'),
|
|
157
161
|
},
|
|
158
162
|
],
|
|
159
163
|
},
|
|
@@ -166,7 +170,7 @@ describe.sequential("Request handler", () => {
|
|
|
166
170
|
expect(result).toMatchSnapshot();
|
|
167
171
|
});
|
|
168
172
|
|
|
169
|
-
it(
|
|
173
|
+
it('should fetch messages', async () => {
|
|
170
174
|
const result = await cluster.sendRequestToNode(leaderId)(API.FETCH, {
|
|
171
175
|
maxWaitMs: 100,
|
|
172
176
|
minBytes: 1,
|
|
@@ -179,7 +183,7 @@ describe.sequential("Request handler", () => {
|
|
|
179
183
|
topicId,
|
|
180
184
|
partitions: [
|
|
181
185
|
{
|
|
182
|
-
partition:
|
|
186
|
+
partition: partitionIndex,
|
|
183
187
|
currentLeaderEpoch: -1,
|
|
184
188
|
fetchOffset: 0n,
|
|
185
189
|
lastFetchedEpoch: 0,
|
|
@@ -190,10 +194,10 @@ describe.sequential("Request handler", () => {
|
|
|
190
194
|
},
|
|
191
195
|
],
|
|
192
196
|
forgottenTopicsData: [],
|
|
193
|
-
rackId:
|
|
197
|
+
rackId: '',
|
|
194
198
|
});
|
|
195
199
|
result.responses.forEach((response) => {
|
|
196
|
-
response.topicId =
|
|
200
|
+
response.topicId = 'Any<UUID>';
|
|
197
201
|
response.partitions.forEach((partition) => {
|
|
198
202
|
partition.records.forEach((record) => {
|
|
199
203
|
expect(record.baseTimestamp).toBeGreaterThan(1721926744730n);
|
|
@@ -211,10 +215,10 @@ describe.sequential("Request handler", () => {
|
|
|
211
215
|
|
|
212
216
|
let coordinatorId = -1;
|
|
213
217
|
|
|
214
|
-
it(
|
|
218
|
+
it('should find coordinator', async () => {
|
|
215
219
|
const result = await cluster.sendRequest(API.FIND_COORDINATOR, { keyType: KEY_TYPE.GROUP, keys: [groupId] });
|
|
216
220
|
result.coordinators.forEach((coordinator) => {
|
|
217
|
-
coordinator.key =
|
|
221
|
+
coordinator.key = 'Any<String>';
|
|
218
222
|
});
|
|
219
223
|
coordinatorId = result.coordinators[0].nodeId;
|
|
220
224
|
result.coordinators.forEach((coordinator) => {
|
|
@@ -224,9 +228,9 @@ describe.sequential("Request handler", () => {
|
|
|
224
228
|
expect(result).toMatchSnapshot();
|
|
225
229
|
});
|
|
226
230
|
|
|
227
|
-
let memberId =
|
|
231
|
+
let memberId = '';
|
|
228
232
|
|
|
229
|
-
it(
|
|
233
|
+
it('should fail join group request with new memberId', async () => {
|
|
230
234
|
try {
|
|
231
235
|
const result = await cluster.sendRequestToNode(coordinatorId)(API.JOIN_GROUP, {
|
|
232
236
|
groupId,
|
|
@@ -234,67 +238,67 @@ describe.sequential("Request handler", () => {
|
|
|
234
238
|
rebalanceTimeoutMs: 60000,
|
|
235
239
|
memberId,
|
|
236
240
|
groupInstanceId: null,
|
|
237
|
-
protocolType:
|
|
241
|
+
protocolType: 'consumer',
|
|
238
242
|
protocols: [
|
|
239
243
|
{
|
|
240
|
-
name:
|
|
241
|
-
metadata: { version: 0, topics: [
|
|
244
|
+
name: 'RoundRobinAssigner',
|
|
245
|
+
metadata: { version: 0, topics: ['kafka-ts-test-topic'] },
|
|
242
246
|
},
|
|
243
247
|
],
|
|
244
248
|
reason: null,
|
|
245
249
|
});
|
|
246
|
-
expect(false,
|
|
250
|
+
expect(false, 'Should throw an error').toBe(true);
|
|
247
251
|
} catch (error) {
|
|
248
252
|
const { response } = error as KafkaTSApiError;
|
|
249
253
|
memberId = response.memberId;
|
|
250
|
-
response.memberId =
|
|
254
|
+
response.memberId = 'Any<UUID>';
|
|
251
255
|
expect(response).toMatchSnapshot();
|
|
252
256
|
}
|
|
253
257
|
});
|
|
254
258
|
|
|
255
|
-
it(
|
|
259
|
+
it('should join group', async () => {
|
|
256
260
|
const result = await cluster.sendRequestToNode(coordinatorId)(API.JOIN_GROUP, {
|
|
257
261
|
groupId,
|
|
258
262
|
sessionTimeoutMs: 30000,
|
|
259
263
|
rebalanceTimeoutMs: 60000,
|
|
260
264
|
memberId,
|
|
261
265
|
groupInstanceId: null,
|
|
262
|
-
protocolType:
|
|
266
|
+
protocolType: 'consumer',
|
|
263
267
|
protocols: [
|
|
264
268
|
{
|
|
265
|
-
name:
|
|
266
|
-
metadata: { version: 0, topics: [
|
|
269
|
+
name: 'RoundRobinAssigner',
|
|
270
|
+
metadata: { version: 0, topics: ['kafka-ts-test-topic'] },
|
|
267
271
|
},
|
|
268
272
|
],
|
|
269
273
|
reason: null,
|
|
270
274
|
});
|
|
271
|
-
result.memberId =
|
|
272
|
-
result.leader =
|
|
275
|
+
result.memberId = 'Any<UUID>';
|
|
276
|
+
result.leader = 'Any<UUID>';
|
|
273
277
|
result.members.forEach((member) => {
|
|
274
|
-
member.memberId =
|
|
278
|
+
member.memberId = 'Any<UUID>';
|
|
275
279
|
});
|
|
276
280
|
expect(result).toMatchSnapshot();
|
|
277
281
|
});
|
|
278
282
|
|
|
279
|
-
it(
|
|
283
|
+
it('should sync group', async () => {
|
|
280
284
|
const result = await cluster.sendRequestToNode(coordinatorId)(API.SYNC_GROUP, {
|
|
281
285
|
groupId,
|
|
282
286
|
generationId: 1,
|
|
283
287
|
memberId,
|
|
284
288
|
groupInstanceId: null,
|
|
285
|
-
protocolType:
|
|
286
|
-
protocolName:
|
|
289
|
+
protocolType: 'consumer',
|
|
290
|
+
protocolName: 'RoundRobinAssigner',
|
|
287
291
|
assignments: [
|
|
288
292
|
{
|
|
289
293
|
memberId,
|
|
290
|
-
assignment: {
|
|
294
|
+
assignment: { 'kafka-test-topic': [0] },
|
|
291
295
|
},
|
|
292
296
|
],
|
|
293
297
|
});
|
|
294
298
|
expect(result).toMatchSnapshot();
|
|
295
299
|
});
|
|
296
300
|
|
|
297
|
-
it(
|
|
301
|
+
it('should commit offsets', async () => {
|
|
298
302
|
const result = await cluster.sendRequestToNode(coordinatorId)(API.OFFSET_COMMIT, {
|
|
299
303
|
groupId,
|
|
300
304
|
generationIdOrMemberEpoch: 1,
|
|
@@ -302,7 +306,7 @@ describe.sequential("Request handler", () => {
|
|
|
302
306
|
groupInstanceId: null,
|
|
303
307
|
topics: [
|
|
304
308
|
{
|
|
305
|
-
name:
|
|
309
|
+
name: 'kafka-ts-test-topic',
|
|
306
310
|
partitions: [
|
|
307
311
|
{ partitionIndex: 0, committedOffset: 1n, committedLeaderEpoch: 0, committedMetadata: null },
|
|
308
312
|
],
|
|
@@ -312,7 +316,7 @@ describe.sequential("Request handler", () => {
|
|
|
312
316
|
expect(result).toMatchSnapshot();
|
|
313
317
|
});
|
|
314
318
|
|
|
315
|
-
it(
|
|
319
|
+
it('should fetch offsets', async () => {
|
|
316
320
|
const result = await cluster.sendRequestToNode(coordinatorId)(API.OFFSET_FETCH, {
|
|
317
321
|
groups: [
|
|
318
322
|
{
|
|
@@ -321,7 +325,7 @@ describe.sequential("Request handler", () => {
|
|
|
321
325
|
memberEpoch: 0,
|
|
322
326
|
topics: [
|
|
323
327
|
{
|
|
324
|
-
name:
|
|
328
|
+
name: 'kafka-ts-test-topic',
|
|
325
329
|
partitionIndexes: [0],
|
|
326
330
|
},
|
|
327
331
|
],
|
|
@@ -330,12 +334,12 @@ describe.sequential("Request handler", () => {
|
|
|
330
334
|
requireStable: false,
|
|
331
335
|
});
|
|
332
336
|
result.groups.forEach((group) => {
|
|
333
|
-
group.groupId =
|
|
337
|
+
group.groupId = 'Any<String>';
|
|
334
338
|
});
|
|
335
339
|
expect(result).toMatchSnapshot();
|
|
336
340
|
});
|
|
337
341
|
|
|
338
|
-
it(
|
|
342
|
+
it('should heartbeat', async () => {
|
|
339
343
|
const result = await cluster.sendRequestToNode(coordinatorId)(API.HEARTBEAT, {
|
|
340
344
|
groupId,
|
|
341
345
|
generationId: 1,
|
|
@@ -345,24 +349,24 @@ describe.sequential("Request handler", () => {
|
|
|
345
349
|
expect(result).toMatchSnapshot();
|
|
346
350
|
});
|
|
347
351
|
|
|
348
|
-
it(
|
|
352
|
+
it('should leave group', async () => {
|
|
349
353
|
const result = await cluster.sendRequestToNode(coordinatorId)(API.LEAVE_GROUP, {
|
|
350
354
|
groupId,
|
|
351
355
|
members: [{ memberId, groupInstanceId: null, reason: null }],
|
|
352
356
|
});
|
|
353
357
|
result.members.forEach((member) => {
|
|
354
|
-
member.memberId =
|
|
358
|
+
member.memberId = 'Any<UUID>';
|
|
355
359
|
});
|
|
356
360
|
expect(result).toMatchSnapshot();
|
|
357
361
|
});
|
|
358
362
|
|
|
359
|
-
it(
|
|
363
|
+
it('should delete topics', async () => {
|
|
360
364
|
const result = await cluster.sendRequest(API.DELETE_TOPICS, {
|
|
361
|
-
topics: [{ name:
|
|
365
|
+
topics: [{ name: 'kafka-ts-test-topic', topicId: null }],
|
|
362
366
|
timeoutMs: 10000,
|
|
363
367
|
});
|
|
364
368
|
result.responses.forEach((response) => {
|
|
365
|
-
response.topicId =
|
|
369
|
+
response.topicId = 'Any<UUID>';
|
|
366
370
|
});
|
|
367
371
|
expect(result).toMatchSnapshot();
|
|
368
372
|
});
|
package/src/cluster.ts
CHANGED
|
@@ -1,87 +1,85 @@
|
|
|
1
|
-
import { TcpSocketConnectOpts } from
|
|
2
|
-
import { TLSSocketOptions } from
|
|
3
|
-
import { API } from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { TcpSocketConnectOpts } from 'net';
|
|
2
|
+
import { TLSSocketOptions } from 'tls';
|
|
3
|
+
import { API } from './api';
|
|
4
|
+
import { Metadata } from './api/metadata';
|
|
5
|
+
import { Broker, SASLProvider } from './broker';
|
|
6
|
+
import { SendRequest } from './connection';
|
|
7
|
+
import { KafkaTSError } from './utils/error';
|
|
8
|
+
import { log } from './utils/logger';
|
|
7
9
|
|
|
8
10
|
type ClusterOptions = {
|
|
9
11
|
clientId: string | null;
|
|
10
12
|
bootstrapServers: TcpSocketConnectOpts[];
|
|
11
|
-
sasl:
|
|
13
|
+
sasl: SASLProvider | null;
|
|
12
14
|
ssl: TLSSocketOptions | null;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export class Cluster {
|
|
16
|
-
private seedBroker:
|
|
18
|
+
private seedBroker = new Broker({ clientId: null, sasl: null, ssl: null, options: { port: 9092 } });
|
|
17
19
|
private brokerById: Record<number, Broker> = {};
|
|
20
|
+
private brokerMetadata: Record<number, Metadata['brokers'][number]> = {};
|
|
18
21
|
|
|
19
|
-
constructor(private options: ClusterOptions) {
|
|
20
|
-
this.seedBroker = new Broker({
|
|
21
|
-
clientId: this.options.clientId,
|
|
22
|
-
sasl: this.options.sasl,
|
|
23
|
-
ssl: this.options.ssl,
|
|
24
|
-
options: this.options.bootstrapServers[0],
|
|
25
|
-
});
|
|
26
|
-
}
|
|
22
|
+
constructor(private options: ClusterOptions) {}
|
|
27
23
|
|
|
28
24
|
public async connect() {
|
|
29
|
-
await this.
|
|
25
|
+
this.seedBroker = await this.findSeedBroker();
|
|
26
|
+
this.brokerById = {};
|
|
27
|
+
|
|
30
28
|
const metadata = await this.sendRequest(API.METADATA, {
|
|
31
29
|
allowTopicAutoCreation: false,
|
|
32
30
|
includeTopicAuthorizedOperations: false,
|
|
33
31
|
topics: [],
|
|
34
32
|
});
|
|
35
|
-
|
|
36
|
-
this.brokerById = Object.fromEntries(
|
|
37
|
-
metadata.brokers.map(({ nodeId, ...options }) => [
|
|
38
|
-
nodeId,
|
|
39
|
-
new Broker({
|
|
40
|
-
clientId: this.options.clientId,
|
|
41
|
-
sasl: this.options.sasl,
|
|
42
|
-
ssl: this.options.ssl,
|
|
43
|
-
options,
|
|
44
|
-
}),
|
|
45
|
-
]),
|
|
46
|
-
);
|
|
47
|
-
return this;
|
|
33
|
+
this.brokerMetadata = Object.fromEntries(metadata.brokers.map((options) => [options.nodeId, options]));
|
|
48
34
|
}
|
|
49
35
|
|
|
50
36
|
public async disconnect() {
|
|
51
|
-
await Promise.all([
|
|
52
|
-
this.seedBroker.disconnect(),
|
|
53
|
-
...Object.values(this.brokerById).map((broker) => broker.disconnect()),
|
|
54
|
-
]);
|
|
37
|
+
await Promise.all([this.seedBroker.disconnect(), ...Object.values(this.brokerById).map((x) => x.disconnect())]);
|
|
55
38
|
}
|
|
56
39
|
|
|
40
|
+
public setSeedBroker = async (nodeId: number) => {
|
|
41
|
+
await this.seedBroker.disconnect();
|
|
42
|
+
this.seedBroker = await this.acquireBroker(nodeId);
|
|
43
|
+
};
|
|
44
|
+
|
|
57
45
|
public sendRequest: SendRequest = (...args) => this.seedBroker.sendRequest(...args);
|
|
58
46
|
|
|
59
47
|
public sendRequestToNode =
|
|
60
48
|
(nodeId: number): SendRequest =>
|
|
61
49
|
async (...args) => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
throw new ConnectionError(`Broker ${nodeId} is not available`);
|
|
50
|
+
if (!this.brokerById[nodeId]) {
|
|
51
|
+
this.brokerById[nodeId] = await this.acquireBroker(nodeId);
|
|
65
52
|
}
|
|
66
|
-
|
|
67
|
-
return broker.sendRequest(...args);
|
|
53
|
+
return this.brokerById[nodeId].sendRequest(...args);
|
|
68
54
|
};
|
|
69
55
|
|
|
70
|
-
|
|
56
|
+
public async acquireBroker(nodeId: number) {
|
|
57
|
+
const broker = new Broker({
|
|
58
|
+
clientId: this.options.clientId,
|
|
59
|
+
sasl: this.options.sasl,
|
|
60
|
+
ssl: this.options.ssl,
|
|
61
|
+
options: this.brokerMetadata[nodeId],
|
|
62
|
+
});
|
|
63
|
+
await broker.connect();
|
|
64
|
+
return broker;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private async findSeedBroker() {
|
|
71
68
|
const randomizedBrokers = this.options.bootstrapServers.toSorted(() => Math.random() - 0.5);
|
|
72
69
|
for (const options of randomizedBrokers) {
|
|
73
70
|
try {
|
|
74
|
-
|
|
71
|
+
const broker = await new Broker({
|
|
75
72
|
clientId: this.options.clientId,
|
|
76
73
|
sasl: this.options.sasl,
|
|
77
74
|
ssl: this.options.ssl,
|
|
78
75
|
options,
|
|
79
|
-
})
|
|
80
|
-
|
|
76
|
+
});
|
|
77
|
+
await broker.connect();
|
|
78
|
+
return broker;
|
|
81
79
|
} catch (error) {
|
|
82
|
-
|
|
80
|
+
log.warn(`Failed to connect to seed broker ${options.host}:${options.port}`, error);
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
|
-
throw new KafkaTSError(
|
|
83
|
+
throw new KafkaTSError('No seed brokers found');
|
|
86
84
|
}
|
|
87
85
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { gzip, unzip } from 'zlib';
|
|
2
|
+
import { Codec } from './types';
|
|
3
|
+
|
|
4
|
+
export const GZIP: Codec = {
|
|
5
|
+
compress: async (data) =>
|
|
6
|
+
new Promise<Buffer>((resolve, reject) => gzip(data, (err, result) => (err ? reject(err) : resolve(result)))),
|
|
7
|
+
decompress: async (data) =>
|
|
8
|
+
new Promise<Buffer>((resolve, reject) => unzip(data, (err, result) => (err ? reject(err) : resolve(result)))),
|
|
9
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GZIP } from './gzip';
|
|
2
|
+
import { NONE } from './none';
|
|
3
|
+
import { Codec } from './types';
|
|
4
|
+
|
|
5
|
+
const codecs: Record<number, Codec> = {
|
|
6
|
+
0: NONE,
|
|
7
|
+
1: GZIP,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const findCodec = (type: number) => {
|
|
11
|
+
const codec = codecs[type];
|
|
12
|
+
if (!codec) {
|
|
13
|
+
throw new Error(`Unsupported codec: ${type}`);
|
|
14
|
+
}
|
|
15
|
+
return codec;
|
|
16
|
+
};
|