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.
Files changed (195) hide show
  1. package/.prettierrc +3 -2
  2. package/README.md +109 -39
  3. package/dist/api/api-versions.d.ts +9 -0
  4. package/dist/api/api-versions.js +24 -0
  5. package/dist/api/create-topics.d.ts +38 -0
  6. package/dist/api/create-topics.js +53 -0
  7. package/dist/api/delete-topics.d.ts +18 -0
  8. package/dist/api/delete-topics.js +33 -0
  9. package/dist/api/fetch.d.ts +84 -0
  10. package/dist/api/fetch.js +142 -0
  11. package/dist/api/find-coordinator.d.ts +21 -0
  12. package/dist/api/find-coordinator.js +39 -0
  13. package/dist/api/heartbeat.d.ts +11 -0
  14. package/dist/api/heartbeat.js +27 -0
  15. package/dist/api/index.d.ts +578 -0
  16. package/dist/api/index.js +165 -0
  17. package/dist/api/init-producer-id.d.ts +13 -0
  18. package/dist/api/init-producer-id.js +29 -0
  19. package/dist/api/join-group.d.ts +34 -0
  20. package/dist/api/join-group.js +51 -0
  21. package/dist/api/leave-group.d.ts +19 -0
  22. package/dist/api/leave-group.js +39 -0
  23. package/dist/api/list-offsets.d.ts +29 -0
  24. package/dist/api/list-offsets.js +48 -0
  25. package/dist/api/metadata.d.ts +40 -0
  26. package/dist/api/metadata.js +58 -0
  27. package/dist/api/offset-commit.d.ts +28 -0
  28. package/dist/api/offset-commit.js +48 -0
  29. package/dist/api/offset-fetch.d.ts +33 -0
  30. package/dist/api/offset-fetch.js +57 -0
  31. package/dist/api/produce.d.ts +54 -0
  32. package/dist/api/produce.js +126 -0
  33. package/dist/api/sasl-authenticate.d.ts +11 -0
  34. package/dist/api/sasl-authenticate.js +23 -0
  35. package/dist/api/sasl-handshake.d.ts +6 -0
  36. package/dist/api/sasl-handshake.js +19 -0
  37. package/dist/api/sync-group.d.ts +24 -0
  38. package/dist/api/sync-group.js +36 -0
  39. package/dist/auth/index.d.ts +2 -0
  40. package/dist/auth/index.js +8 -0
  41. package/dist/auth/plain.d.ts +5 -0
  42. package/dist/auth/plain.js +12 -0
  43. package/dist/auth/scram.d.ts +9 -0
  44. package/dist/auth/scram.js +40 -0
  45. package/dist/broker.d.ts +30 -0
  46. package/dist/broker.js +55 -0
  47. package/dist/client.d.ts +23 -0
  48. package/dist/client.js +36 -0
  49. package/dist/cluster.d.ts +27 -0
  50. package/dist/cluster.js +70 -0
  51. package/dist/cluster.test.d.ts +1 -0
  52. package/dist/cluster.test.js +345 -0
  53. package/dist/codecs/gzip.d.ts +2 -0
  54. package/dist/codecs/gzip.js +8 -0
  55. package/dist/codecs/index.d.ts +2 -0
  56. package/dist/codecs/index.js +17 -0
  57. package/dist/codecs/none.d.ts +2 -0
  58. package/dist/codecs/none.js +7 -0
  59. package/dist/codecs/types.d.ts +5 -0
  60. package/dist/codecs/types.js +2 -0
  61. package/dist/connection.d.ts +26 -0
  62. package/dist/connection.js +175 -0
  63. package/dist/consumer/consumer-group.d.ts +41 -0
  64. package/dist/consumer/consumer-group.js +217 -0
  65. package/dist/consumer/consumer-metadata.d.ts +7 -0
  66. package/dist/consumer/consumer-metadata.js +14 -0
  67. package/dist/consumer/consumer.d.ts +44 -0
  68. package/dist/consumer/consumer.js +225 -0
  69. package/dist/consumer/fetch-manager.d.ts +33 -0
  70. package/dist/consumer/fetch-manager.js +140 -0
  71. package/dist/consumer/fetcher.d.ts +25 -0
  72. package/dist/consumer/fetcher.js +64 -0
  73. package/dist/consumer/offset-manager.d.ts +22 -0
  74. package/dist/consumer/offset-manager.js +66 -0
  75. package/dist/consumer/processor.d.ts +19 -0
  76. package/dist/consumer/processor.js +59 -0
  77. package/dist/distributors/assignments-to-replicas.d.ts +16 -0
  78. package/dist/distributors/assignments-to-replicas.js +59 -0
  79. package/dist/distributors/assignments-to-replicas.test.d.ts +1 -0
  80. package/dist/distributors/assignments-to-replicas.test.js +40 -0
  81. package/dist/distributors/messages-to-topic-partition-leaders.d.ts +17 -0
  82. package/dist/distributors/messages-to-topic-partition-leaders.js +15 -0
  83. package/dist/distributors/messages-to-topic-partition-leaders.test.d.ts +1 -0
  84. package/dist/distributors/messages-to-topic-partition-leaders.test.js +30 -0
  85. package/dist/distributors/partitioner.d.ts +7 -0
  86. package/dist/distributors/partitioner.js +23 -0
  87. package/dist/index.d.ts +9 -0
  88. package/dist/index.js +26 -0
  89. package/dist/metadata.d.ts +24 -0
  90. package/dist/metadata.js +106 -0
  91. package/dist/producer/producer.d.ts +24 -0
  92. package/dist/producer/producer.js +131 -0
  93. package/dist/types.d.ts +11 -0
  94. package/dist/types.js +2 -0
  95. package/dist/utils/api.d.ts +9 -0
  96. package/dist/utils/api.js +5 -0
  97. package/dist/utils/crypto.d.ts +8 -0
  98. package/dist/utils/crypto.js +18 -0
  99. package/dist/utils/decoder.d.ts +30 -0
  100. package/dist/utils/decoder.js +152 -0
  101. package/dist/utils/delay.d.ts +1 -0
  102. package/dist/utils/delay.js +5 -0
  103. package/dist/utils/encoder.d.ts +28 -0
  104. package/dist/utils/encoder.js +125 -0
  105. package/dist/utils/error.d.ts +11 -0
  106. package/dist/utils/error.js +27 -0
  107. package/dist/utils/logger.d.ts +9 -0
  108. package/dist/utils/logger.js +32 -0
  109. package/dist/utils/memo.d.ts +1 -0
  110. package/dist/utils/memo.js +16 -0
  111. package/dist/utils/murmur2.d.ts +3 -0
  112. package/dist/utils/murmur2.js +40 -0
  113. package/dist/utils/retrier.d.ts +10 -0
  114. package/dist/utils/retrier.js +22 -0
  115. package/dist/utils/tracer.d.ts +5 -0
  116. package/dist/utils/tracer.js +39 -0
  117. package/package.json +30 -19
  118. package/src/__snapshots__/{request-handler.test.ts.snap → cluster.test.ts.snap} +329 -26
  119. package/src/api/api-versions.ts +2 -2
  120. package/src/api/create-topics.ts +2 -2
  121. package/src/api/delete-topics.ts +2 -2
  122. package/src/api/fetch.ts +86 -31
  123. package/src/api/find-coordinator.ts +2 -2
  124. package/src/api/heartbeat.ts +2 -2
  125. package/src/api/index.ts +21 -19
  126. package/src/api/init-producer-id.ts +2 -2
  127. package/src/api/join-group.ts +3 -3
  128. package/src/api/leave-group.ts +2 -2
  129. package/src/api/list-offsets.ts +3 -3
  130. package/src/api/metadata.ts +3 -3
  131. package/src/api/offset-commit.ts +2 -2
  132. package/src/api/offset-fetch.ts +2 -2
  133. package/src/api/produce.ts +17 -20
  134. package/src/api/sasl-authenticate.ts +2 -2
  135. package/src/api/sasl-handshake.ts +2 -2
  136. package/src/api/sync-group.ts +2 -2
  137. package/src/auth/index.ts +2 -0
  138. package/src/auth/plain.ts +10 -0
  139. package/src/auth/scram.ts +52 -0
  140. package/src/broker.ts +12 -14
  141. package/src/client.ts +7 -7
  142. package/src/cluster.test.ts +78 -74
  143. package/src/cluster.ts +43 -45
  144. package/src/codecs/gzip.ts +9 -0
  145. package/src/codecs/index.ts +16 -0
  146. package/src/codecs/none.ts +6 -0
  147. package/src/codecs/types.ts +4 -0
  148. package/src/connection.ts +49 -33
  149. package/src/consumer/consumer-group.ts +57 -35
  150. package/src/consumer/consumer-metadata.ts +2 -2
  151. package/src/consumer/consumer.ts +115 -92
  152. package/src/consumer/fetch-manager.ts +169 -0
  153. package/src/consumer/fetcher.ts +64 -0
  154. package/src/consumer/offset-manager.ts +24 -13
  155. package/src/consumer/processor.ts +53 -0
  156. package/src/distributors/assignments-to-replicas.test.ts +7 -7
  157. package/src/distributors/assignments-to-replicas.ts +2 -4
  158. package/src/distributors/messages-to-topic-partition-leaders.test.ts +6 -6
  159. package/src/distributors/partitioner.ts +27 -0
  160. package/src/index.ts +9 -3
  161. package/src/metadata.ts +8 -4
  162. package/src/producer/producer.ts +30 -20
  163. package/src/types.ts +5 -3
  164. package/src/utils/api.ts +5 -5
  165. package/src/utils/crypto.ts +15 -0
  166. package/src/utils/decoder.ts +14 -8
  167. package/src/utils/encoder.ts +34 -27
  168. package/src/utils/error.ts +3 -3
  169. package/src/utils/logger.ts +37 -0
  170. package/src/utils/murmur2.ts +44 -0
  171. package/src/utils/retrier.ts +1 -1
  172. package/src/utils/tracer.ts +41 -20
  173. package/tsconfig.json +16 -16
  174. package/.github/workflows/release.yml +0 -17
  175. package/certs/ca.crt +0 -29
  176. package/certs/ca.key +0 -52
  177. package/certs/ca.srl +0 -1
  178. package/certs/kafka.crt +0 -29
  179. package/certs/kafka.csr +0 -26
  180. package/certs/kafka.key +0 -52
  181. package/certs/kafka.keystore.jks +0 -0
  182. package/certs/kafka.truststore.jks +0 -0
  183. package/docker-compose.yml +0 -104
  184. package/examples/package-lock.json +0 -31
  185. package/examples/package.json +0 -14
  186. package/examples/src/client.ts +0 -9
  187. package/examples/src/consumer.ts +0 -17
  188. package/examples/src/create-topic.ts +0 -37
  189. package/examples/src/producer.ts +0 -24
  190. package/examples/src/replicator.ts +0 -25
  191. package/examples/src/utils/json.ts +0 -1
  192. package/examples/tsconfig.json +0 -7
  193. package/log4j.properties +0 -95
  194. package/scripts/generate-certs.sh +0 -24
  195. package/src/utils/debug.ts +0 -9
package/src/api/fetch.ts CHANGED
@@ -1,12 +1,15 @@
1
- import { createApi } from "../utils/api";
2
- import { Decoder } from "../utils/decoder";
3
- import { KafkaTSApiError } from "../utils/error";
1
+ import { findCodec } from '../codecs';
2
+ import { createApi } from '../utils/api';
3
+ import { Decoder } from '../utils/decoder';
4
+ import { KafkaTSApiError } from '../utils/error';
4
5
 
5
6
  export const enum IsolationLevel {
6
7
  READ_UNCOMMITTED = 0,
7
8
  READ_COMMITTED = 1,
8
9
  }
9
10
 
11
+ export type FetchResponse = Awaited<ReturnType<(typeof FETCH)['response']>>;
12
+
10
13
  export const FETCH = createApi({
11
14
  apiKey: 1,
12
15
  apiVersion: 16,
@@ -68,7 +71,7 @@ export const FETCH = createApi({
68
71
  )
69
72
  .writeCompactString(data.rackId)
70
73
  .writeUVarInt(0),
71
- response: (decoder) => {
74
+ response: async (decoder) => {
72
75
  const result = {
73
76
  _tag: decoder.readTagBuffer(),
74
77
  throttleTimeMs: decoder.readInt32(),
@@ -88,56 +91,108 @@ export const FETCH = createApi({
88
91
  _tag: abortedTransaction.readTagBuffer(),
89
92
  })),
90
93
  preferredReadReplica: partition.readInt32(),
91
- records: decodeRecords(partition),
94
+ records: decodeRecordBatch(partition),
92
95
  _tag: partition.readTagBuffer(),
93
96
  })),
94
97
  _tag: response.readTagBuffer(),
95
98
  })),
96
99
  _tag2: decoder.readTagBuffer(),
97
100
  };
101
+
98
102
  if (result.errorCode) throw new KafkaTSApiError(result.errorCode, null, result);
99
103
  result.responses.forEach((response) => {
100
104
  response.partitions.forEach((partition) => {
101
105
  if (partition.errorCode) throw new KafkaTSApiError(partition.errorCode, null, result);
102
106
  });
103
107
  });
104
- return result;
108
+
109
+ const decompressedResponses = await Promise.all(
110
+ result.responses.map(async (response) => ({
111
+ ...response,
112
+ partitions: await Promise.all(
113
+ response.partitions.map(async (partition) => ({
114
+ ...partition,
115
+ records: await Promise.all(
116
+ partition.records.map(async ({ recordsLength, compressedRecords, ...record }) => {
117
+ const { decompress } = findCodec(record.compression);
118
+ const decompressedRecords = await decompress(compressedRecords);
119
+ const decompressedDecoder = new Decoder(
120
+ Buffer.concat([recordsLength, decompressedRecords]),
121
+ );
122
+ return { ...record, records: decodeRecord(decompressedDecoder) };
123
+ }),
124
+ ),
125
+ })),
126
+ ),
127
+ })),
128
+ );
129
+
130
+ return { ...result, responses: decompressedResponses };
105
131
  },
106
132
  });
107
133
 
108
- const decodeRecords = (decoder: Decoder) => {
134
+ const decodeRecordBatch = (decoder: Decoder) => {
109
135
  const size = decoder.readUVarInt() - 1;
110
136
  if (size <= 0) {
111
137
  return [];
112
138
  }
113
139
 
140
+ const recordBatchDecoder = new Decoder(decoder.read(size));
141
+
114
142
  const results = [];
115
- while (decoder.getBufferLength() > decoder.getOffset() + 49) {
143
+ while (recordBatchDecoder.getBufferLength() > recordBatchDecoder.getOffset() + 12) {
144
+ const baseOffset = recordBatchDecoder.readInt64();
145
+ const batchLength = recordBatchDecoder.readInt32();
146
+ if (!batchLength) {
147
+ continue;
148
+ }
149
+
150
+ const batchDecoder = new Decoder(recordBatchDecoder.read(batchLength));
151
+
152
+ const result = {
153
+ baseOffset,
154
+ batchLength,
155
+ partitionLeaderEpoch: batchDecoder.readInt32(),
156
+ magic: batchDecoder.readInt8(),
157
+ crc: batchDecoder.readUInt32(),
158
+ attributes: batchDecoder.readInt16(),
159
+ lastOffsetDelta: batchDecoder.readInt32(),
160
+ baseTimestamp: batchDecoder.readInt64(),
161
+ maxTimestamp: batchDecoder.readInt64(),
162
+ producerId: batchDecoder.readInt64(),
163
+ producerEpoch: batchDecoder.readInt16(),
164
+ baseSequence: batchDecoder.readInt32(),
165
+ recordsLength: batchDecoder.read(4),
166
+ compressedRecords: batchDecoder.read(),
167
+ };
168
+
169
+ const compression = result.attributes & 0x07;
170
+ const timestampType = (result.attributes & 0x08) >> 3 ? 'LogAppendTime' : 'CreateTime';
171
+ const isTransactional = !!((result.attributes & 0x10) >> 4);
172
+ const isControlBatch = !!((result.attributes & 0x20) >> 5);
173
+ const hasDeleteHorizonMs = !!((result.attributes & 0x40) >> 6);
174
+
116
175
  results.push({
117
- baseOffset: decoder.readInt64(),
118
- batchLength: decoder.readInt32(),
119
- partitionLeaderEpoch: decoder.readInt32(),
120
- magic: decoder.readInt8(),
121
- crc: decoder.readUInt32(),
122
- attributes: decoder.readInt16(),
123
- lastOffsetDelta: decoder.readInt32(),
124
- baseTimestamp: decoder.readInt64(),
125
- maxTimestamp: decoder.readInt64(),
126
- producerId: decoder.readInt64(),
127
- producerEpoch: decoder.readInt16(),
128
- baseSequence: decoder.readInt32(),
129
- records: decoder.readRecords((record) => ({
130
- attributes: record.readInt8(),
131
- timestampDelta: record.readVarLong(),
132
- offsetDelta: record.readVarInt(),
133
- key: record.readVarIntString(),
134
- value: record.readVarIntString(),
135
- headers: record.readCompactArray((header) => ({
136
- key: header.readVarIntString(),
137
- value: header.readVarIntString(),
138
- })),
139
- })),
176
+ ...result,
177
+ compression,
178
+ timestampType,
179
+ isTransactional,
180
+ isControlBatch,
181
+ hasDeleteHorizonMs,
140
182
  });
141
183
  }
142
184
  return results;
143
185
  };
186
+
187
+ const decodeRecord = (decoder: Decoder) =>
188
+ decoder.readRecords((record) => ({
189
+ attributes: record.readInt8(),
190
+ timestampDelta: record.readVarLong(),
191
+ offsetDelta: record.readVarInt(),
192
+ key: record.readVarIntBuffer(),
193
+ value: record.readVarIntBuffer(),
194
+ headers: record.readVarIntArray((header) => ({
195
+ key: header.readVarIntBuffer(),
196
+ value: header.readVarIntBuffer(),
197
+ })),
198
+ }));
@@ -1,5 +1,5 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
3
 
4
4
  export const KEY_TYPE = {
5
5
  GROUP: 0,
@@ -1,5 +1,5 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
3
 
4
4
  export const HEARTBEAT = createApi({
5
5
  apiKey: 12,
package/src/api/index.ts CHANGED
@@ -1,21 +1,21 @@
1
- import { Api } from "../utils/api";
2
- import { API_VERSIONS } from "./api-versions";
3
- import { CREATE_TOPICS } from "./create-topics";
4
- import { DELETE_TOPICS } from "./delete-topics";
5
- import { FETCH } from "./fetch";
6
- import { FIND_COORDINATOR } from "./find-coordinator";
7
- import { HEARTBEAT } from "./heartbeat";
8
- import { INIT_PRODUCER_ID } from "./init-producer-id";
9
- import { JOIN_GROUP } from "./join-group";
10
- import { LEAVE_GROUP } from "./leave-group";
11
- import { LIST_OFFSETS } from "./list-offsets";
12
- import { METADATA } from "./metadata";
13
- import { OFFSET_COMMIT } from "./offset-commit";
14
- import { OFFSET_FETCH } from "./offset-fetch";
15
- import { PRODUCE } from "./produce";
16
- import { SASL_AUTHENTICATE } from "./sasl-authenticate";
17
- import { SASL_HANDSHAKE } from "./sasl-handshake";
18
- import { SYNC_GROUP } from "./sync-group";
1
+ import { Api } from '../utils/api';
2
+ import { API_VERSIONS } from './api-versions';
3
+ import { CREATE_TOPICS } from './create-topics';
4
+ import { DELETE_TOPICS } from './delete-topics';
5
+ import { FETCH } from './fetch';
6
+ import { FIND_COORDINATOR } from './find-coordinator';
7
+ import { HEARTBEAT } from './heartbeat';
8
+ import { INIT_PRODUCER_ID } from './init-producer-id';
9
+ import { JOIN_GROUP } from './join-group';
10
+ import { LEAVE_GROUP } from './leave-group';
11
+ import { LIST_OFFSETS } from './list-offsets';
12
+ import { METADATA } from './metadata';
13
+ import { OFFSET_COMMIT } from './offset-commit';
14
+ import { OFFSET_FETCH } from './offset-fetch';
15
+ import { PRODUCE } from './produce';
16
+ import { SASL_AUTHENTICATE } from './sasl-authenticate';
17
+ import { SASL_HANDSHAKE } from './sasl-handshake';
18
+ import { SYNC_GROUP } from './sync-group';
19
19
 
20
20
  export const API = {
21
21
  API_VERSIONS,
@@ -37,7 +37,9 @@ export const API = {
37
37
  SYNC_GROUP,
38
38
  };
39
39
 
40
- export const getApiName = (api: Api<unknown, unknown>) => Object.entries(API).find(([, v]) => v === api)?.[0];
40
+ const apiNameByKey = Object.fromEntries(Object.entries(API).map(([k, v]) => [v.apiKey, k]));
41
+
42
+ export const getApiName = <Request, Response>(api: Api<Request, Response>) => apiNameByKey[api.apiKey];
41
43
 
42
44
  export const API_ERROR = {
43
45
  UNKNOWN_SERVER_ERROR: -1,
@@ -1,5 +1,5 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
3
 
4
4
  export const INIT_PRODUCER_ID = createApi({
5
5
  apiKey: 22,
@@ -1,6 +1,6 @@
1
- import { createApi } from "../utils/api";
2
- import { Encoder } from "../utils/encoder";
3
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { Encoder } from '../utils/encoder';
3
+ import { KafkaTSApiError } from '../utils/error';
4
4
 
5
5
  export const JOIN_GROUP = createApi({
6
6
  apiKey: 11,
@@ -1,5 +1,5 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
3
 
4
4
  export const LEAVE_GROUP = createApi({
5
5
  apiKey: 13,
@@ -1,6 +1,6 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
3
- import { IsolationLevel } from "./fetch";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
+ import { IsolationLevel } from './fetch';
4
4
 
5
5
  export const LIST_OFFSETS = createApi({
6
6
  apiKey: 2,
@@ -1,7 +1,7 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
3
 
4
- export type Metadata = ReturnType<(typeof METADATA)["response"]>;
4
+ export type Metadata = Awaited<ReturnType<(typeof METADATA)['response']>>;
5
5
 
6
6
  export const METADATA = createApi({
7
7
  apiKey: 3,
@@ -1,5 +1,5 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
3
 
4
4
  export const OFFSET_COMMIT = createApi({
5
5
  apiKey: 8,
@@ -1,5 +1,5 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
3
 
4
4
  export const OFFSET_FETCH = createApi({
5
5
  apiKey: 9,
@@ -1,6 +1,6 @@
1
- import { createApi } from "../utils/api.js";
2
- import { Encoder } from "../utils/encoder.js";
3
- import { KafkaTSApiError } from "../utils/error.js";
1
+ import { createApi } from '../utils/api.js';
2
+ import { Encoder } from '../utils/encoder.js';
3
+ import { KafkaTSApiError } from '../utils/error.js';
4
4
 
5
5
  export const PRODUCE = createApi({
6
6
  apiKey: 0,
@@ -28,11 +28,11 @@ export const PRODUCE = createApi({
28
28
  attributes: number;
29
29
  timestampDelta: bigint;
30
30
  offsetDelta: number;
31
- key: string | null;
32
- value: string | null;
31
+ key: Buffer | null;
32
+ value: Buffer | null;
33
33
  headers: {
34
- key: string;
35
- value: string;
34
+ key: Buffer;
35
+ value: Buffer;
36
36
  }[];
37
37
  }[];
38
38
  }[];
@@ -61,14 +61,13 @@ export const PRODUCE = createApi({
61
61
  .writeInt8(record.attributes)
62
62
  .writeVarLong(record.timestampDelta)
63
63
  .writeVarInt(record.offsetDelta)
64
- .writeVarIntString(record.key)
65
- .writeVarIntString(record.value)
64
+ .writeVarIntBuffer(record.key)
65
+ .writeVarIntBuffer(record.value)
66
66
  .writeVarIntArray(record.headers, (encoder, header) =>
67
- encoder.writeVarIntString(header.key).writeVarIntString(header.value),
68
- )
69
- .value();
67
+ encoder.writeVarIntBuffer(header.key).writeVarIntBuffer(header.value),
68
+ );
70
69
 
71
- return encoder.writeVarInt(recordBody.length).write(recordBody);
70
+ return encoder.writeVarInt(recordBody.getByteLength()).writeEncoder(recordBody);
72
71
  })
73
72
  .value();
74
73
 
@@ -76,19 +75,17 @@ export const PRODUCE = createApi({
76
75
  .writeInt32(partition.partitionLeaderEpoch)
77
76
  .writeInt8(2) // magic byte
78
77
  .writeUInt32(unsigned(crc32C(batchBody)))
79
- .write(batchBody)
80
- .value();
78
+ .write(batchBody);
81
79
 
82
80
  const batch = new Encoder()
83
81
  .writeInt64(partition.baseOffset)
84
- .writeInt32(batchHeader.length)
85
- .write(batchHeader)
86
- .value();
82
+ .writeInt32(batchHeader.getByteLength())
83
+ .writeEncoder(batchHeader);
87
84
 
88
85
  return encoder
89
86
  .writeInt32(partition.index)
90
- .writeUVarInt(batch.length + 1) // batch size
91
- .write(batch)
87
+ .writeUVarInt(batch.getByteLength() + 1) // batch size
88
+ .writeEncoder(batch)
92
89
  .writeUVarInt(0);
93
90
  })
94
91
  .writeUVarInt(0),
@@ -1,5 +1,5 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
3
 
4
4
  export const SASL_AUTHENTICATE = createApi({
5
5
  apiKey: 36,
@@ -1,5 +1,5 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
3
 
4
4
  export const SASL_HANDSHAKE = createApi({
5
5
  apiKey: 17,
@@ -1,5 +1,5 @@
1
- import { createApi } from "../utils/api";
2
- import { KafkaTSApiError } from "../utils/error";
1
+ import { createApi } from '../utils/api';
2
+ import { KafkaTSApiError } from '../utils/error';
3
3
 
4
4
  export type Assignment = { [topic: string]: number[] };
5
5
 
@@ -0,0 +1,2 @@
1
+ export { saslPlain } from './plain';
2
+ export { saslScramSha256, saslScramSha512 } from './scram';
@@ -0,0 +1,10 @@
1
+ import { API } from "../api";
2
+ import { SASLProvider } from "../broker";
3
+
4
+ export const saslPlain = ({ username, password }: { username: string; password: string }): SASLProvider => ({
5
+ mechanism: 'PLAIN',
6
+ authenticate: async ({ sendRequest }) => {
7
+ const authBytes = [null, username, password].join('\u0000');
8
+ await sendRequest(API.SASL_AUTHENTICATE, { authBytes: Buffer.from(authBytes) });
9
+ },
10
+ });
@@ -0,0 +1,52 @@
1
+ import { API } from '../api';
2
+ import { SASLProvider } from '../broker';
3
+ import { base64Decode, base64Encode, generateNonce, hash, hmac, saltPassword, xor } from '../utils/crypto';
4
+ import { KafkaTSError } from '../utils/error';
5
+
6
+ const saslScram =
7
+ ({ mechanism, keyLength, digest }: { mechanism: string; keyLength: number; digest: string }) =>
8
+ ({ username, password }: { username: string; password: string }): SASLProvider => ({
9
+ mechanism,
10
+ authenticate: async ({ sendRequest }) => {
11
+ const nonce = generateNonce();
12
+ const firstMessage = `n=${username},r=${nonce}`;
13
+
14
+ const { authBytes } = await sendRequest(API.SASL_AUTHENTICATE, {
15
+ authBytes: Buffer.from(`n,,${firstMessage}`),
16
+ });
17
+ if (!authBytes) {
18
+ throw new KafkaTSError('No auth response');
19
+ }
20
+
21
+ const response = Object.fromEntries(
22
+ authBytes
23
+ .toString()
24
+ .split(',')
25
+ .map((pair) => pair.split('=')),
26
+ ) as { r: string; s: string; i: string };
27
+
28
+ const rnonce = response.r;
29
+ if (!rnonce.startsWith(nonce)) {
30
+ throw new KafkaTSError('Invalid nonce');
31
+ }
32
+ const iterations = parseInt(response.i);
33
+ const salt = base64Decode(response.s);
34
+
35
+ const saltedPassword = await saltPassword(password, salt, iterations, keyLength, digest);
36
+ const clientKey = hmac(saltedPassword, 'Client Key', digest);
37
+ const clientKeyHash = hash(clientKey, digest);
38
+
39
+ let finalMessage = `c=${base64Encode('n,,')},r=${rnonce}`;
40
+
41
+ const fullMessage = `${firstMessage},${authBytes.toString()},${finalMessage}`;
42
+ const clientSignature = hmac(clientKeyHash, fullMessage, digest);
43
+ const clientProof = base64Encode(xor(clientKey, clientSignature));
44
+
45
+ finalMessage += `,p=${clientProof}`;
46
+
47
+ await sendRequest(API.SASL_AUTHENTICATE, { authBytes: Buffer.from(finalMessage) });
48
+ },
49
+ });
50
+
51
+ export const saslScramSha256 = saslScram({ mechanism: 'SCRAM-SHA-256', keyLength: 32, digest: 'sha256' });
52
+ export const saslScramSha512 = saslScram({ mechanism: 'SCRAM-SHA-512', keyLength: 64, digest: 'sha512' });
package/src/broker.ts CHANGED
@@ -1,16 +1,19 @@
1
- import { TcpSocketConnectOpts } from "net";
2
- import { TLSSocketOptions } from "tls";
3
- import { API } from "./api";
4
- import { Connection, SendRequest } from "./connection";
5
- import { KafkaTSError } from "./utils/error";
6
- import { memo } from "./utils/memo";
1
+ import { TcpSocketConnectOpts } from 'net';
2
+ import { TLSSocketOptions } from 'tls';
3
+ import { API } from './api';
4
+ import { Connection, SendRequest } from './connection';
5
+ import { KafkaTSError } from './utils/error';
6
+ import { memo } from './utils/memo';
7
7
 
8
- export type SASLOptions = { mechanism: "PLAIN"; username: string; password: string };
8
+ export type SASLProvider = {
9
+ mechanism: string;
10
+ authenticate: (context: { sendRequest: SendRequest }) => Promise<void>;
11
+ };
9
12
 
10
13
  type BrokerOptions = {
11
14
  clientId: string | null;
12
15
  options: TcpSocketConnectOpts;
13
- sasl: SASLOptions | null;
16
+ sasl: SASLProvider | null;
14
17
  ssl: TLSSocketOptions | null;
15
18
  };
16
19
 
@@ -64,11 +67,6 @@ export class Broker {
64
67
  }
65
68
 
66
69
  private async saslAuthenticate() {
67
- if (this.options.sasl?.mechanism !== "PLAIN") {
68
- return;
69
- }
70
- const { username, password } = this.options.sasl;
71
- const authBytes = [null, username, password].join("\u0000");
72
- await this.sendRequest(API.SASL_AUTHENTICATE, { authBytes: Buffer.from(authBytes) });
70
+ await this.options.sasl?.authenticate({ sendRequest: this.sendRequest });
73
71
  }
74
72
  }
package/src/client.ts CHANGED
@@ -1,14 +1,14 @@
1
- import { TcpSocketConnectOpts } from "net";
2
- import { TLSSocketOptions } from "tls";
3
- import { SASLOptions } from "./broker";
4
- import { Cluster } from "./cluster";
5
- import { Consumer, ConsumerOptions } from "./consumer/consumer";
6
- import { Producer, ProducerOptions } from "./producer/producer";
1
+ import { TcpSocketConnectOpts } from 'net';
2
+ import { TLSSocketOptions } from 'tls';
3
+ import { SASLProvider } from './broker';
4
+ import { Cluster } from './cluster';
5
+ import { Consumer, ConsumerOptions } from './consumer/consumer';
6
+ import { Producer, ProducerOptions } from './producer/producer';
7
7
 
8
8
  type ClientOptions = {
9
9
  clientId?: string | null;
10
10
  bootstrapServers: TcpSocketConnectOpts[];
11
- sasl?: SASLOptions | null;
11
+ sasl?: SASLProvider | null;
12
12
  ssl?: TLSSocketOptions | null;
13
13
  };
14
14