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
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.FetchManager = void 0;
13
+ const error_1 = require("../utils/error");
14
+ const tracer_1 = require("../utils/tracer");
15
+ const fetcher_1 = require("./fetcher");
16
+ const processor_1 = require("./processor");
17
+ const trace = (0, tracer_1.createTracer)('FetchManager');
18
+ class FetchManager {
19
+ options;
20
+ queue = [];
21
+ isRunning = false;
22
+ fetchers;
23
+ processors;
24
+ pollQueue = [];
25
+ fetcherCallbacks = {};
26
+ constructor(options) {
27
+ this.options = options;
28
+ const { fetch, process, consumerGroup, nodeAssignments, concurrency } = this.options;
29
+ this.fetchers = nodeAssignments.map(({ nodeId, assignment }, index) => new fetcher_1.Fetcher(index, {
30
+ nodeId,
31
+ assignment,
32
+ consumerGroup,
33
+ fetch,
34
+ onResponse: this.onResponse.bind(this),
35
+ }));
36
+ this.processors = Array.from({ length: concurrency }).map(() => new processor_1.Processor({ process, poll: this.poll.bind(this) }));
37
+ }
38
+ async start() {
39
+ this.queue = [];
40
+ this.isRunning = true;
41
+ try {
42
+ await Promise.all([
43
+ ...this.fetchers.map((fetcher) => fetcher.loop()),
44
+ ...this.processors.map((processor) => processor.loop()),
45
+ ]);
46
+ }
47
+ finally {
48
+ await this.stop();
49
+ }
50
+ }
51
+ async stop() {
52
+ this.isRunning = false;
53
+ const stopPromise = Promise.all([
54
+ ...this.fetchers.map((fetcher) => fetcher.stop()),
55
+ ...this.processors.map((processor) => processor.stop()),
56
+ ]);
57
+ this.pollQueue.forEach((resolve) => resolve());
58
+ this.pollQueue = [];
59
+ Object.values(this.fetcherCallbacks).forEach((callback) => callback());
60
+ this.fetcherCallbacks = {};
61
+ await stopPromise;
62
+ }
63
+ async poll() {
64
+ if (!this.isRunning) {
65
+ return [];
66
+ }
67
+ const batch = this.queue.shift();
68
+ if (!batch) {
69
+ // wait until new data is available or fetch manager is requested to stop
70
+ await new Promise((resolve) => {
71
+ this.pollQueue.push(resolve);
72
+ });
73
+ return this.poll();
74
+ }
75
+ if ('kind' in batch && batch.kind === 'checkpoint') {
76
+ this.fetcherCallbacks[batch.fetcherId]?.();
77
+ return this.poll();
78
+ }
79
+ this.pollQueue?.shift()?.();
80
+ return batch;
81
+ }
82
+ async onResponse(fetcherId, response) {
83
+ const { metadata, batchGranularity } = this.options;
84
+ const batches = fetchResponseToBatches(response, batchGranularity, metadata);
85
+ if (!batches.length) {
86
+ return;
87
+ }
88
+ // wait until all broker batches have been processed or fetch manager is requested to stop
89
+ await new Promise((resolve) => {
90
+ this.fetcherCallbacks[fetcherId] = resolve;
91
+ this.queue.push(...batches, { kind: 'checkpoint', fetcherId });
92
+ this.pollQueue?.shift()?.();
93
+ });
94
+ }
95
+ }
96
+ exports.FetchManager = FetchManager;
97
+ __decorate([
98
+ trace(() => ({ root: true })),
99
+ __metadata("design:type", Function),
100
+ __metadata("design:paramtypes", []),
101
+ __metadata("design:returntype", Promise)
102
+ ], FetchManager.prototype, "start", null);
103
+ __decorate([
104
+ trace(),
105
+ __metadata("design:type", Function),
106
+ __metadata("design:paramtypes", []),
107
+ __metadata("design:returntype", Promise)
108
+ ], FetchManager.prototype, "poll", null);
109
+ __decorate([
110
+ trace(),
111
+ __metadata("design:type", Function),
112
+ __metadata("design:paramtypes", [Number, Object]),
113
+ __metadata("design:returntype", Promise)
114
+ ], FetchManager.prototype, "onResponse", null);
115
+ const fetchResponseToBatches = (batch, batchGranularity, metadata) => {
116
+ const brokerTopics = batch.responses.map(({ topicId, partitions }) => partitions.map(({ partitionIndex, records }) => records.flatMap(({ baseTimestamp, baseOffset, records }) => records.map((message) => ({
117
+ topic: metadata.getTopicNameById(topicId),
118
+ partition: partitionIndex,
119
+ key: message.key ?? null,
120
+ value: message.value ?? null,
121
+ headers: Object.fromEntries(message.headers.map(({ key, value }) => [key, value])),
122
+ timestamp: baseTimestamp + BigInt(message.timestampDelta),
123
+ offset: baseOffset + BigInt(message.offsetDelta),
124
+ })))));
125
+ switch (batchGranularity) {
126
+ case 'broker':
127
+ const messages = brokerTopics.flatMap((topicPartition) => topicPartition.flatMap((partitionMessages) => partitionMessages));
128
+ return messages.length ? [messages] : [];
129
+ case 'topic':
130
+ return brokerTopics
131
+ .map((topicPartition) => topicPartition.flatMap((partitionMessages) => partitionMessages))
132
+ .filter((messages) => messages.length);
133
+ case 'partition':
134
+ return brokerTopics
135
+ .flatMap((topicPartition) => topicPartition.map((partitionMessages) => partitionMessages))
136
+ .filter((messages) => messages.length);
137
+ default:
138
+ throw new error_1.KafkaTSError(`Unhandled batch granularity: ${batchGranularity}`);
139
+ }
140
+ };
@@ -0,0 +1,25 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { EventEmitter } from 'stream';
4
+ import { FetchResponse } from '../api/fetch';
5
+ import { Assignment } from '../api/sync-group';
6
+ import { ConsumerGroup } from './consumer-group';
7
+ type FetcherOptions = {
8
+ nodeId: number;
9
+ assignment: Assignment;
10
+ consumerGroup?: ConsumerGroup;
11
+ fetch: (nodeId: number, assignment: Assignment) => Promise<FetchResponse>;
12
+ onResponse: (fetcherId: number, response: FetchResponse) => Promise<void>;
13
+ };
14
+ export declare class Fetcher extends EventEmitter<{
15
+ stopped: [];
16
+ }> {
17
+ private fetcherId;
18
+ private options;
19
+ private isRunning;
20
+ constructor(fetcherId: number, options: FetcherOptions);
21
+ loop(): Promise<void>;
22
+ private step;
23
+ stop(): Promise<void>;
24
+ }
25
+ export {};
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Fetcher = void 0;
13
+ const stream_1 = require("stream");
14
+ const tracer_1 = require("../utils/tracer");
15
+ const trace = (0, tracer_1.createTracer)('Fetcher');
16
+ class Fetcher extends stream_1.EventEmitter {
17
+ fetcherId;
18
+ options;
19
+ isRunning = false;
20
+ constructor(fetcherId, options) {
21
+ super();
22
+ this.fetcherId = fetcherId;
23
+ this.options = options;
24
+ }
25
+ async loop() {
26
+ this.isRunning = true;
27
+ try {
28
+ while (this.isRunning) {
29
+ await this.step();
30
+ }
31
+ }
32
+ finally {
33
+ this.isRunning = false;
34
+ this.emit('stopped');
35
+ }
36
+ }
37
+ async step() {
38
+ const { nodeId, assignment, consumerGroup, fetch, onResponse } = this.options;
39
+ const response = await fetch(nodeId, assignment);
40
+ if (!this.isRunning) {
41
+ return;
42
+ }
43
+ consumerGroup?.handleLastHeartbeat();
44
+ await onResponse(this.fetcherId, response);
45
+ consumerGroup?.handleLastHeartbeat();
46
+ }
47
+ async stop() {
48
+ if (!this.isRunning) {
49
+ return;
50
+ }
51
+ const stopPromise = new Promise((resolve) => {
52
+ this.once('stopped', resolve);
53
+ });
54
+ this.isRunning = false;
55
+ return stopPromise;
56
+ }
57
+ }
58
+ exports.Fetcher = Fetcher;
59
+ __decorate([
60
+ trace(),
61
+ __metadata("design:type", Function),
62
+ __metadata("design:paramtypes", []),
63
+ __metadata("design:returntype", Promise)
64
+ ], Fetcher.prototype, "step", null);
@@ -0,0 +1,22 @@
1
+ import { IsolationLevel } from '../api/fetch';
2
+ import { Cluster } from '../cluster';
3
+ import { ConsumerMetadata } from './consumer-metadata';
4
+ type OffsetManagerOptions = {
5
+ cluster: Cluster;
6
+ metadata: ConsumerMetadata;
7
+ isolationLevel: IsolationLevel;
8
+ };
9
+ export declare class OffsetManager {
10
+ private options;
11
+ private currentOffsets;
12
+ pendingOffsets: Record<string, Record<number, bigint>>;
13
+ constructor(options: OffsetManagerOptions);
14
+ getCurrentOffset(topic: string, partition: number): bigint;
15
+ resolve(topic: string, partition: number, offset: bigint): void;
16
+ flush(topicPartitions: Record<string, Set<number>>): void;
17
+ fetchOffsets(options: {
18
+ fromBeginning: boolean;
19
+ }): Promise<void>;
20
+ private listOffsets;
21
+ }
22
+ export {};
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OffsetManager = void 0;
4
+ const api_1 = require("../api");
5
+ const messages_to_topic_partition_leaders_1 = require("../distributors/messages-to-topic-partition-leaders");
6
+ const tracer_1 = require("../utils/tracer");
7
+ const trace = (0, tracer_1.createTracer)('OffsetManager');
8
+ class OffsetManager {
9
+ options;
10
+ currentOffsets = {};
11
+ pendingOffsets = {};
12
+ constructor(options) {
13
+ this.options = options;
14
+ }
15
+ getCurrentOffset(topic, partition) {
16
+ return this.currentOffsets[topic]?.[partition] ?? 0n;
17
+ }
18
+ resolve(topic, partition, offset) {
19
+ this.pendingOffsets[topic] ??= {};
20
+ this.pendingOffsets[topic][partition] = offset;
21
+ }
22
+ flush(topicPartitions) {
23
+ Object.entries(topicPartitions).forEach(([topic, partitions]) => {
24
+ this.currentOffsets[topic] ??= {};
25
+ partitions.forEach((partition) => {
26
+ if (this.pendingOffsets[topic]?.[partition]) {
27
+ this.currentOffsets[topic][partition] = this.pendingOffsets[topic][partition];
28
+ delete this.pendingOffsets[topic][partition];
29
+ }
30
+ });
31
+ });
32
+ }
33
+ async fetchOffsets(options) {
34
+ const { metadata } = this.options;
35
+ const topicPartitions = Object.entries(metadata.getAssignment()).flatMap(([topic, partitions]) => partitions.map((partition) => ({ topic, partition })));
36
+ const nodeTopicPartitions = (0, messages_to_topic_partition_leaders_1.distributeMessagesToTopicPartitionLeaders)(topicPartitions, metadata.getTopicPartitionLeaderIds());
37
+ await Promise.all(Object.entries(nodeTopicPartitions).map(([nodeId, topicPartitions]) => this.listOffsets({
38
+ ...options,
39
+ nodeId: parseInt(nodeId),
40
+ nodeAssignment: Object.fromEntries(Object.entries(topicPartitions).map(([topicName, partitions]) => [topicName, Object.keys(partitions).map(Number)])),
41
+ })));
42
+ }
43
+ async listOffsets({ nodeId, nodeAssignment, fromBeginning, }) {
44
+ const { cluster, isolationLevel } = this.options;
45
+ const offsets = await cluster.sendRequestToNode(nodeId)(api_1.API.LIST_OFFSETS, {
46
+ replicaId: -1,
47
+ isolationLevel,
48
+ topics: Object.entries(nodeAssignment)
49
+ .flatMap(([topic, partitions]) => partitions.map((partition) => ({ topic, partition })))
50
+ .map(({ topic, partition }) => ({
51
+ name: topic,
52
+ partitions: [{ partitionIndex: partition, currentLeaderEpoch: -1, timestamp: -1n }],
53
+ })),
54
+ });
55
+ const topicPartitions = {};
56
+ offsets.topics.forEach(({ name, partitions }) => {
57
+ topicPartitions[name] ??= new Set();
58
+ partitions.forEach(({ partitionIndex, offset }) => {
59
+ topicPartitions[name].add(partitionIndex);
60
+ this.resolve(name, partitionIndex, fromBeginning ? 0n : offset);
61
+ });
62
+ });
63
+ this.flush(topicPartitions);
64
+ }
65
+ }
66
+ exports.OffsetManager = OffsetManager;
@@ -0,0 +1,19 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { EventEmitter } from 'stream';
4
+ import { Batch } from '../types';
5
+ type ProcessorOptions = {
6
+ poll: () => Promise<Batch>;
7
+ process: (batch: Batch) => Promise<void>;
8
+ };
9
+ export declare class Processor extends EventEmitter<{
10
+ stopped: [];
11
+ }> {
12
+ private options;
13
+ private isRunning;
14
+ constructor(options: ProcessorOptions);
15
+ loop(): Promise<void>;
16
+ private step;
17
+ stop(): Promise<void>;
18
+ }
19
+ export {};
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Processor = void 0;
13
+ const stream_1 = require("stream");
14
+ const tracer_1 = require("../utils/tracer");
15
+ const trace = (0, tracer_1.createTracer)('Processor');
16
+ class Processor extends stream_1.EventEmitter {
17
+ options;
18
+ isRunning = false;
19
+ constructor(options) {
20
+ super();
21
+ this.options = options;
22
+ }
23
+ async loop() {
24
+ this.isRunning = true;
25
+ try {
26
+ while (this.isRunning) {
27
+ await this.step();
28
+ }
29
+ }
30
+ finally {
31
+ this.isRunning = false;
32
+ this.emit('stopped');
33
+ }
34
+ }
35
+ async step() {
36
+ const { poll, process } = this.options;
37
+ const batch = await poll();
38
+ if (batch.length) {
39
+ await process(batch);
40
+ }
41
+ }
42
+ async stop() {
43
+ if (!this.isRunning) {
44
+ return;
45
+ }
46
+ const stopPromise = new Promise((resolve) => {
47
+ this.once('stopped', resolve);
48
+ });
49
+ this.isRunning = false;
50
+ return stopPromise;
51
+ }
52
+ }
53
+ exports.Processor = Processor;
54
+ __decorate([
55
+ trace(),
56
+ __metadata("design:type", Function),
57
+ __metadata("design:paramtypes", []),
58
+ __metadata("design:returntype", Promise)
59
+ ], Processor.prototype, "step", null);
@@ -0,0 +1,16 @@
1
+ type Assignment = {
2
+ [topicName: string]: number[];
3
+ };
4
+ type TopicPartitionReplicaIds = {
5
+ [topicName: string]: {
6
+ [partition: number]: number[];
7
+ };
8
+ };
9
+ type NodeAssignment = {
10
+ [replicaId: number]: Assignment;
11
+ };
12
+ /** From replica ids pick the one with fewest assignments to balance the load across brokers */
13
+ export declare const distributeAssignmentsToNodesBalanced: (assignment: Assignment, topicPartitionReplicaIds: TopicPartitionReplicaIds) => NodeAssignment;
14
+ /** Minimize the total number of replicas in the result to reduce the number of requests to different brokers */
15
+ export declare const distributeAssignmentsToNodesOptimized: (assignment: Assignment, topicPartitionReplicaIds: TopicPartitionReplicaIds) => NodeAssignment;
16
+ export {};
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.distributeAssignmentsToNodesOptimized = exports.distributeAssignmentsToNodesBalanced = void 0;
4
+ /** From replica ids pick the one with fewest assignments to balance the load across brokers */
5
+ const distributeAssignmentsToNodesBalanced = (assignment, topicPartitionReplicaIds) => {
6
+ const replicaPartitions = getPartitionsByReplica(assignment, topicPartitionReplicaIds);
7
+ const result = {};
8
+ for (const [topicName, partitions] of Object.entries(assignment)) {
9
+ for (const partition of partitions) {
10
+ const replicaIds = topicPartitionReplicaIds[topicName][partition];
11
+ const replicaId = replicaIds.reduce((prev, curr) => {
12
+ if (!prev) {
13
+ return curr;
14
+ }
15
+ return (replicaPartitions[prev]?.length ?? 0) < (replicaPartitions[curr]?.length ?? 0) ? prev : curr;
16
+ });
17
+ result[replicaId] ??= {};
18
+ result[replicaId][topicName] ??= [];
19
+ result[replicaId][topicName].push(partition);
20
+ }
21
+ }
22
+ return result;
23
+ };
24
+ exports.distributeAssignmentsToNodesBalanced = distributeAssignmentsToNodesBalanced;
25
+ /** Minimize the total number of replicas in the result to reduce the number of requests to different brokers */
26
+ const distributeAssignmentsToNodesOptimized = (assignment, topicPartitionReplicaIds) => {
27
+ const result = {};
28
+ const sortFn = ([, partitionsA], [, partitionsB]) => partitionsB.length - partitionsA.length;
29
+ let replicaPartitions = getPartitionsByReplica(assignment, topicPartitionReplicaIds);
30
+ while (replicaPartitions.length) {
31
+ replicaPartitions.sort(sortFn);
32
+ const [replicaId, partitions] = replicaPartitions.shift();
33
+ if (!partitions.length) {
34
+ continue;
35
+ }
36
+ result[parseInt(replicaId)] = partitions.reduce((acc, partition) => {
37
+ const [topicName, partitionId] = partition.split(':');
38
+ acc[topicName] ??= [];
39
+ acc[topicName].push(parseInt(partitionId));
40
+ return acc;
41
+ }, {});
42
+ replicaPartitions = replicaPartitions.map(([replicaId, replicaPartitions]) => [replicaId, replicaPartitions.filter((partition) => !partitions.includes(partition))]);
43
+ }
44
+ return result;
45
+ };
46
+ exports.distributeAssignmentsToNodesOptimized = distributeAssignmentsToNodesOptimized;
47
+ const getPartitionsByReplica = (assignment, topicPartitionReplicaIds) => {
48
+ const partitionsByReplicaId = {};
49
+ for (const [topicName, partitions] of Object.entries(assignment)) {
50
+ for (const partition of partitions) {
51
+ const replicaIds = topicPartitionReplicaIds[topicName][partition];
52
+ for (const replicaId of replicaIds) {
53
+ partitionsByReplicaId[replicaId] ??= [];
54
+ partitionsByReplicaId[replicaId].push(`${topicName}:${partition}`);
55
+ }
56
+ }
57
+ }
58
+ return Object.entries(partitionsByReplicaId);
59
+ };
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const assignments_to_replicas_1 = require("./assignments-to-replicas");
5
+ (0, vitest_1.describe)('Distribute assignments to replica ids', () => {
6
+ (0, vitest_1.describe)('distributeAssignmentsToNodesBalanced', () => {
7
+ (0, vitest_1.it)('smoke', () => {
8
+ const result = (0, assignments_to_replicas_1.distributeAssignmentsToNodesBalanced)({ topic: [0, 1] }, { topic: { 0: [0, 1], 1: [1, 2] } });
9
+ (0, vitest_1.expect)(result).toMatchInlineSnapshot(`
10
+ {
11
+ "1": {
12
+ "topic": [
13
+ 0,
14
+ ],
15
+ },
16
+ "2": {
17
+ "topic": [
18
+ 1,
19
+ ],
20
+ },
21
+ }
22
+ `);
23
+ });
24
+ });
25
+ (0, vitest_1.describe)('distributeAssignmentsToNodesOptimized', () => {
26
+ (0, vitest_1.it)('smoke', () => {
27
+ const result = (0, assignments_to_replicas_1.distributeAssignmentsToNodesOptimized)({ topic: [0, 1] }, { topic: { 0: [0, 1], 1: [1, 2] } });
28
+ (0, vitest_1.expect)(result).toMatchInlineSnapshot(`
29
+ {
30
+ "1": {
31
+ "topic": [
32
+ 0,
33
+ 1,
34
+ ],
35
+ },
36
+ }
37
+ `);
38
+ });
39
+ });
40
+ });
@@ -0,0 +1,17 @@
1
+ type TopicPartitionLeader = {
2
+ [topicName: string]: {
3
+ [partitionId: number]: number;
4
+ };
5
+ };
6
+ type MessagesByNodeTopicPartition<T> = {
7
+ [nodeId: number]: {
8
+ [topicName: string]: {
9
+ [partitionId: number]: T[];
10
+ };
11
+ };
12
+ };
13
+ export declare const distributeMessagesToTopicPartitionLeaders: <T extends {
14
+ topic: string;
15
+ partition: number;
16
+ }>(messages: T[], topicPartitionLeader: TopicPartitionLeader) => MessagesByNodeTopicPartition<T>;
17
+ export {};
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.distributeMessagesToTopicPartitionLeaders = void 0;
4
+ const distributeMessagesToTopicPartitionLeaders = (messages, topicPartitionLeader) => {
5
+ const result = {};
6
+ messages.forEach((message) => {
7
+ const leaderId = topicPartitionLeader[message.topic][message.partition];
8
+ result[leaderId] ??= {};
9
+ result[leaderId][message.topic] ??= {};
10
+ result[leaderId][message.topic][message.partition] ??= [];
11
+ result[leaderId][message.topic][message.partition].push(message);
12
+ });
13
+ return result;
14
+ };
15
+ exports.distributeMessagesToTopicPartitionLeaders = distributeMessagesToTopicPartitionLeaders;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const messages_to_topic_partition_leaders_1 = require("./messages-to-topic-partition-leaders");
5
+ (0, vitest_1.describe)('Distribute messages to partition leader ids', () => {
6
+ (0, vitest_1.describe)('distributeMessagesToTopicPartitionLeaders', () => {
7
+ (0, vitest_1.it)('snoke', () => {
8
+ const result = (0, messages_to_topic_partition_leaders_1.distributeMessagesToTopicPartitionLeaders)([{ topic: 'topic', partition: 0, key: null, value: null, offset: 0n, timestamp: 0n, headers: {} }], { topic: { 0: 1 } });
9
+ (0, vitest_1.expect)(result).toMatchInlineSnapshot(`
10
+ {
11
+ "1": {
12
+ "topic": {
13
+ "0": [
14
+ {
15
+ "headers": {},
16
+ "key": null,
17
+ "offset": 0n,
18
+ "partition": 0,
19
+ "timestamp": 0n,
20
+ "topic": "topic",
21
+ "value": null,
22
+ },
23
+ ],
24
+ },
25
+ },
26
+ }
27
+ `);
28
+ });
29
+ });
30
+ });
@@ -0,0 +1,7 @@
1
+ import { Metadata } from '../metadata';
2
+ import { Message } from '../types';
3
+ export type Partition = (message: Message) => number;
4
+ export type Partitioner = (context: {
5
+ metadata: Metadata;
6
+ }) => Partition;
7
+ export declare const defaultPartitioner: Partitioner;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultPartitioner = void 0;
4
+ const murmur2_1 = require("../utils/murmur2");
5
+ const defaultPartitioner = ({ metadata }) => {
6
+ const topicCounterMap = {};
7
+ const getNextValue = (topic) => {
8
+ topicCounterMap[topic] ??= 0;
9
+ return topicCounterMap[topic]++;
10
+ };
11
+ return ({ topic, partition, key }) => {
12
+ if (partition !== null && partition !== undefined) {
13
+ return partition;
14
+ }
15
+ const partitions = metadata.getTopicPartitions()[topic];
16
+ const numPartitions = partitions.length;
17
+ if (key) {
18
+ return (0, murmur2_1.toPositive)((0, murmur2_1.murmur2)(key)) % numPartitions;
19
+ }
20
+ return (0, murmur2_1.toPositive)(getNextValue(topic)) % numPartitions;
21
+ };
22
+ };
23
+ exports.defaultPartitioner = defaultPartitioner;
@@ -0,0 +1,9 @@
1
+ export * from './api';
2
+ export * from './auth';
3
+ export { SASLProvider } from './broker';
4
+ export * from './client';
5
+ export * from './distributors/partitioner';
6
+ export * from './types';
7
+ export * from './utils/error';
8
+ export * from './utils/logger';
9
+ export { Tracer, setTracer } from './utils/tracer';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.setTracer = void 0;
18
+ __exportStar(require("./api"), exports);
19
+ __exportStar(require("./auth"), exports);
20
+ __exportStar(require("./client"), exports);
21
+ __exportStar(require("./distributors/partitioner"), exports);
22
+ __exportStar(require("./types"), exports);
23
+ __exportStar(require("./utils/error"), exports);
24
+ __exportStar(require("./utils/logger"), exports);
25
+ var tracer_1 = require("./utils/tracer");
26
+ Object.defineProperty(exports, "setTracer", { enumerable: true, get: function () { return tracer_1.setTracer; } });