kafka-ts 1.0.3 → 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
@@ -141,6 +141,7 @@ The existing high-level libraries (e.g. kafkajs) are missing a few crucial featu
141
141
  | bootstrapServers | TcpSocketConnectOpts[] | true | | List of kafka brokers for initial cluster discovery. |
142
142
  | sasl | SASLProvider | false | | SASL provider |
143
143
  | ssl | TLSSocketOptions | false | | SSL configuration. |
144
+ | requestTimeout | number | false | 60000 | Request timeout in milliseconds. |
144
145
 
145
146
  #### Supported SASL mechanisms
146
147
 
package/dist/broker.d.ts CHANGED
@@ -14,6 +14,7 @@ type BrokerOptions = {
14
14
  options: TcpSocketConnectOpts;
15
15
  sasl: SASLProvider | null;
16
16
  ssl: TLSSocketOptions | null;
17
+ requestTimeout: number;
17
18
  };
18
19
  export declare class Broker {
19
20
  private options;
package/dist/broker.js CHANGED
@@ -14,6 +14,7 @@ class Broker {
14
14
  clientId: this.options.clientId,
15
15
  connection: this.options.options,
16
16
  ssl: this.options.ssl,
17
+ requestTimeout: this.options.requestTimeout,
17
18
  });
18
19
  this.sendRequest = this.connection.sendRequest.bind(this.connection);
19
20
  }
package/dist/client.d.ts CHANGED
@@ -11,6 +11,7 @@ export type ClientOptions = {
11
11
  bootstrapServers: TcpSocketConnectOpts[];
12
12
  sasl?: SASLProvider | null;
13
13
  ssl?: TLSSocketOptions | null;
14
+ requestTimeout?: number;
14
15
  };
15
16
  export declare class Client {
16
17
  private options;
package/dist/client.js CHANGED
@@ -12,6 +12,7 @@ class Client {
12
12
  clientId: options.clientId ?? null,
13
13
  sasl: options.sasl ?? null,
14
14
  ssl: options.ssl ?? null,
15
+ requestTimeout: options.requestTimeout ?? 60_000,
15
16
  };
16
17
  }
17
18
  async startConsumer(options) {
@@ -28,6 +29,7 @@ class Client {
28
29
  bootstrapServers: this.options.bootstrapServers,
29
30
  sasl: this.options.sasl,
30
31
  ssl: this.options.ssl,
32
+ requestTimeout: this.options.requestTimeout,
31
33
  });
32
34
  }
33
35
  }
package/dist/cluster.d.ts CHANGED
@@ -9,6 +9,7 @@ type ClusterOptions = {
9
9
  bootstrapServers: TcpSocketConnectOpts[];
10
10
  sasl: SASLProvider | null;
11
11
  ssl: TLSSocketOptions | null;
12
+ requestTimeout: number;
12
13
  };
13
14
  export declare class Cluster {
14
15
  private options;
package/dist/cluster.js CHANGED
@@ -54,6 +54,7 @@ class Cluster {
54
54
  clientId: this.options.clientId,
55
55
  sasl: this.options.sasl,
56
56
  ssl: this.options.ssl,
57
+ requestTimeout: this.options.requestTimeout,
57
58
  options: this.brokerMetadata[nodeId],
58
59
  });
59
60
  await broker.connect();
@@ -63,10 +64,11 @@ class Cluster {
63
64
  const randomizedBrokers = this.options.bootstrapServers.toSorted(() => Math.random() - 0.5);
64
65
  for (const options of randomizedBrokers) {
65
66
  try {
66
- const broker = await new broker_1.Broker({
67
+ const broker = new broker_1.Broker({
67
68
  clientId: this.options.clientId,
68
69
  sasl: this.options.sasl,
69
70
  ssl: this.options.ssl,
71
+ requestTimeout: this.options.requestTimeout,
70
72
  options,
71
73
  });
72
74
  await broker.connect();
@@ -7,6 +7,7 @@ type ConnectionOptions = {
7
7
  clientId: string | null;
8
8
  connection: TcpSocketConnectOpts;
9
9
  ssl: TLSSocketOptions | null;
10
+ requestTimeout: number;
10
11
  };
11
12
  export declare class Connection {
12
13
  private options;
@@ -105,7 +105,7 @@ class Connection {
105
105
  timeout = setTimeout(() => {
106
106
  delete this.queue[correlationId];
107
107
  reject(new error_1.ConnectionError(`${apiName} timed out`));
108
- }, 30_000);
108
+ }, this.options.requestTimeout);
109
109
  try {
110
110
  this.queue[correlationId] = { resolve, reject };
111
111
  await this.write(requestEncoder.value());
@@ -13,6 +13,7 @@ export declare class Producer {
13
13
  private producerEpoch;
14
14
  private sequences;
15
15
  private partition;
16
+ private lock;
16
17
  constructor(cluster: Cluster, options: ProducerOptions);
17
18
  send(messages: Message[], { acks }?: {
18
19
  acks?: -1 | 1;
@@ -16,6 +16,7 @@ const partitioner_1 = require("../distributors/partitioner");
16
16
  const metadata_1 = require("../metadata");
17
17
  const delay_1 = require("../utils/delay");
18
18
  const error_1 = require("../utils/error");
19
+ const lock_1 = require("../utils/lock");
19
20
  const tracer_1 = require("../utils/tracer");
20
21
  const trace = (0, tracer_1.createTracer)('Producer');
21
22
  class Producer {
@@ -26,6 +27,7 @@ class Producer {
26
27
  producerEpoch = 0;
27
28
  sequences = {};
28
29
  partition;
30
+ lock = new lock_1.Lock();
29
31
  constructor(cluster, options) {
30
32
  this.cluster = cluster;
31
33
  this.options = {
@@ -49,54 +51,57 @@ class Producer {
49
51
  const nodeTopicPartitionMessages = (0, messages_to_topic_partition_leaders_1.distributeMessagesToTopicPartitionLeaders)(partitionedMessages, this.metadata.getTopicPartitionLeaderIds());
50
52
  try {
51
53
  await Promise.all(Object.entries(nodeTopicPartitionMessages).map(async ([nodeId, topicPartitionMessages]) => {
52
- const topicData = Object.entries(topicPartitionMessages).map(([topic, partitionMessages]) => ({
53
- name: topic,
54
- partitionData: Object.entries(partitionMessages).map(([partition, messages]) => {
55
- const partitionIndex = parseInt(partition);
56
- let baseTimestamp;
57
- let maxTimestamp;
58
- messages.forEach(({ timestamp = defaultTimestamp }) => {
59
- if (!baseTimestamp || timestamp < baseTimestamp) {
60
- baseTimestamp = timestamp;
61
- }
62
- if (!maxTimestamp || timestamp > maxTimestamp) {
63
- maxTimestamp = timestamp;
64
- }
65
- });
66
- return {
67
- index: partitionIndex,
68
- baseOffset: 0n,
69
- partitionLeaderEpoch: -1,
70
- attributes: 0,
71
- lastOffsetDelta: messages.length - 1,
72
- baseTimestamp: baseTimestamp ?? 0n,
73
- maxTimestamp: maxTimestamp ?? 0n,
74
- producerId: this.producerId,
75
- producerEpoch: 0,
76
- baseSequence: this.getSequence(topic, partitionIndex),
77
- records: messages.map((message, index) => ({
54
+ const lockKeys = Object.entries(topicPartitionMessages).flatMap(([topic, partitionMessages]) => Object.entries(partitionMessages).map(([partition]) => `${topic}-${partition}`));
55
+ await this.lock.acquire(lockKeys, async () => {
56
+ const topicData = Object.entries(topicPartitionMessages).map(([topic, partitionMessages]) => ({
57
+ name: topic,
58
+ partitionData: Object.entries(partitionMessages).map(([partition, messages]) => {
59
+ const partitionIndex = parseInt(partition);
60
+ let baseTimestamp;
61
+ let maxTimestamp;
62
+ messages.forEach(({ timestamp = defaultTimestamp }) => {
63
+ if (!baseTimestamp || timestamp < baseTimestamp) {
64
+ baseTimestamp = timestamp;
65
+ }
66
+ if (!maxTimestamp || timestamp > maxTimestamp) {
67
+ maxTimestamp = timestamp;
68
+ }
69
+ });
70
+ return {
71
+ index: partitionIndex,
72
+ baseOffset: 0n,
73
+ partitionLeaderEpoch: -1,
78
74
  attributes: 0,
79
- timestampDelta: (message.timestamp ?? defaultTimestamp) - (baseTimestamp ?? 0n),
80
- offsetDelta: index,
81
- key: message.key ?? null,
82
- value: message.value,
83
- headers: Object.entries(message.headers ?? {}).map(([key, value]) => ({
84
- key,
85
- value,
75
+ lastOffsetDelta: messages.length - 1,
76
+ baseTimestamp: baseTimestamp ?? 0n,
77
+ maxTimestamp: maxTimestamp ?? 0n,
78
+ producerId: this.producerId,
79
+ producerEpoch: 0,
80
+ baseSequence: this.getSequence(topic, partitionIndex),
81
+ records: messages.map((message, index) => ({
82
+ attributes: 0,
83
+ timestampDelta: (message.timestamp ?? defaultTimestamp) - (baseTimestamp ?? 0n),
84
+ offsetDelta: index,
85
+ key: message.key ?? null,
86
+ value: message.value,
87
+ headers: Object.entries(message.headers ?? {}).map(([key, value]) => ({
88
+ key,
89
+ value,
90
+ })),
86
91
  })),
87
- })),
88
- };
89
- }),
90
- }));
91
- await this.cluster.sendRequestToNode(parseInt(nodeId))(api_1.API.PRODUCE, {
92
- transactionalId: null,
93
- acks,
94
- timeoutMs: 5000,
95
- topicData,
96
- });
97
- topicData.forEach(({ name, partitionData }) => {
98
- partitionData.forEach(({ index, records }) => {
99
- this.updateSequence(name, index, records.length);
92
+ };
93
+ }),
94
+ }));
95
+ await this.cluster.sendRequestToNode(parseInt(nodeId))(api_1.API.PRODUCE, {
96
+ transactionalId: null,
97
+ acks,
98
+ timeoutMs: 5000,
99
+ topicData,
100
+ });
101
+ topicData.forEach(({ name, partitionData }) => {
102
+ partitionData.forEach(({ index, records }) => {
103
+ this.updateSequence(name, index, records.length);
104
+ });
100
105
  });
101
106
  });
102
107
  }));
@@ -0,0 +1,9 @@
1
+ /// <reference types="node" />
2
+ import EventEmitter from 'events';
3
+ export declare class Lock extends EventEmitter {
4
+ private locks;
5
+ constructor();
6
+ acquire(keys: string[], callback: () => Promise<void>): Promise<void>;
7
+ private acquireKey;
8
+ private releaseKey;
9
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Lock = void 0;
7
+ const events_1 = __importDefault(require("events"));
8
+ const logger_1 = require("./logger");
9
+ class Lock extends events_1.default {
10
+ locks = {};
11
+ constructor() {
12
+ super();
13
+ this.setMaxListeners(Infinity);
14
+ }
15
+ async acquire(keys, callback) {
16
+ await Promise.all(keys.map((key) => this.acquireKey(key)));
17
+ try {
18
+ await callback();
19
+ }
20
+ finally {
21
+ keys.forEach((key) => this.releaseKey(key));
22
+ }
23
+ }
24
+ async acquireKey(key) {
25
+ while (this.locks[key]) {
26
+ await new Promise((resolve) => {
27
+ const timeout = setTimeout(() => {
28
+ logger_1.log.warn(`Lock timed out`, { key });
29
+ this.emit(`release:${key}`);
30
+ }, 10_000);
31
+ this.once(`release:${key}`, () => {
32
+ clearTimeout(timeout);
33
+ resolve();
34
+ });
35
+ });
36
+ }
37
+ this.locks[key] = true;
38
+ }
39
+ releaseKey(key) {
40
+ this.locks[key] = false;
41
+ this.emit(`release:${key}`);
42
+ }
43
+ }
44
+ exports.Lock = Lock;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kafka-ts",
3
- "version": "1.0.3",
3
+ "version": "1.1.1",
4
4
  "main": "dist/index.js",
5
5
  "author": "Priit Käärd",
6
6
  "license": "MIT",