kafka-ts 0.0.3-beta → 0.0.4

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 (176) hide show
  1. package/README.md +72 -8
  2. package/dist/api/api-versions.d.ts +9 -0
  3. package/{src/api/api-versions.ts → dist/api/api-versions.js} +8 -5
  4. package/dist/api/create-topics.d.ts +38 -0
  5. package/dist/api/create-topics.js +53 -0
  6. package/dist/api/delete-topics.d.ts +18 -0
  7. package/dist/api/delete-topics.js +33 -0
  8. package/dist/api/fetch.d.ts +84 -0
  9. package/dist/api/fetch.js +142 -0
  10. package/dist/api/find-coordinator.d.ts +21 -0
  11. package/{src/api/find-coordinator.ts → dist/api/find-coordinator.js} +14 -14
  12. package/dist/api/heartbeat.d.ts +11 -0
  13. package/dist/api/heartbeat.js +27 -0
  14. package/dist/api/index.d.ts +576 -0
  15. package/{src/api/index.ts → dist/api/index.js} +42 -41
  16. package/dist/api/init-producer-id.d.ts +13 -0
  17. package/dist/api/init-producer-id.js +29 -0
  18. package/dist/api/join-group.d.ts +34 -0
  19. package/dist/api/join-group.js +51 -0
  20. package/dist/api/leave-group.d.ts +19 -0
  21. package/dist/api/leave-group.js +39 -0
  22. package/dist/api/list-offsets.d.ts +29 -0
  23. package/dist/api/list-offsets.js +48 -0
  24. package/dist/api/metadata.d.ts +40 -0
  25. package/{src/api/metadata.ts → dist/api/metadata.js} +18 -26
  26. package/dist/api/offset-commit.d.ts +28 -0
  27. package/dist/api/offset-commit.js +48 -0
  28. package/dist/api/offset-fetch.d.ts +31 -0
  29. package/dist/api/offset-fetch.js +55 -0
  30. package/dist/api/produce.d.ts +54 -0
  31. package/{src/api/produce.ts → dist/api/produce.js} +55 -102
  32. package/dist/api/sasl-authenticate.d.ts +11 -0
  33. package/dist/api/sasl-authenticate.js +23 -0
  34. package/dist/api/sasl-handshake.d.ts +6 -0
  35. package/dist/api/sasl-handshake.js +19 -0
  36. package/dist/api/sync-group.d.ts +24 -0
  37. package/dist/api/sync-group.js +36 -0
  38. package/dist/auth/index.d.ts +2 -0
  39. package/dist/auth/index.js +8 -0
  40. package/dist/auth/plain.d.ts +5 -0
  41. package/dist/auth/plain.js +12 -0
  42. package/dist/auth/scram.d.ts +9 -0
  43. package/dist/auth/scram.js +40 -0
  44. package/dist/broker.d.ts +30 -0
  45. package/dist/broker.js +55 -0
  46. package/dist/client.d.ts +22 -0
  47. package/dist/client.js +36 -0
  48. package/dist/cluster.d.ts +27 -0
  49. package/dist/cluster.js +70 -0
  50. package/dist/cluster.test.d.ts +1 -0
  51. package/{src/cluster.test.ts → dist/cluster.test.js} +87 -113
  52. package/dist/codecs/gzip.d.ts +2 -0
  53. package/dist/codecs/gzip.js +8 -0
  54. package/dist/codecs/index.d.ts +2 -0
  55. package/dist/codecs/index.js +17 -0
  56. package/dist/codecs/none.d.ts +2 -0
  57. package/dist/codecs/none.js +7 -0
  58. package/dist/codecs/types.d.ts +5 -0
  59. package/dist/codecs/types.js +2 -0
  60. package/dist/connection.d.ts +26 -0
  61. package/dist/connection.js +175 -0
  62. package/dist/consumer/consumer-group.d.ts +41 -0
  63. package/dist/consumer/consumer-group.js +215 -0
  64. package/dist/consumer/consumer-metadata.d.ts +7 -0
  65. package/dist/consumer/consumer-metadata.js +14 -0
  66. package/dist/consumer/consumer.d.ts +44 -0
  67. package/dist/consumer/consumer.js +225 -0
  68. package/dist/consumer/fetch-manager.d.ts +33 -0
  69. package/dist/consumer/fetch-manager.js +140 -0
  70. package/dist/consumer/fetcher.d.ts +25 -0
  71. package/dist/consumer/fetcher.js +64 -0
  72. package/dist/consumer/offset-manager.d.ts +22 -0
  73. package/dist/consumer/offset-manager.js +66 -0
  74. package/dist/consumer/processor.d.ts +19 -0
  75. package/dist/consumer/processor.js +59 -0
  76. package/dist/distributors/assignments-to-replicas.d.ts +16 -0
  77. package/{src/distributors/assignments-to-replicas.ts → dist/distributors/assignments-to-replicas.js} +15 -41
  78. package/dist/distributors/assignments-to-replicas.test.d.ts +1 -0
  79. package/dist/distributors/assignments-to-replicas.test.js +40 -0
  80. package/dist/distributors/messages-to-topic-partition-leaders.d.ts +17 -0
  81. package/dist/distributors/messages-to-topic-partition-leaders.js +15 -0
  82. package/dist/distributors/messages-to-topic-partition-leaders.test.d.ts +1 -0
  83. package/dist/distributors/messages-to-topic-partition-leaders.test.js +30 -0
  84. package/dist/distributors/partitioner.d.ts +7 -0
  85. package/dist/distributors/partitioner.js +23 -0
  86. package/dist/index.d.ts +9 -0
  87. package/dist/index.js +26 -0
  88. package/dist/metadata.d.ts +24 -0
  89. package/dist/metadata.js +106 -0
  90. package/dist/producer/producer.d.ts +24 -0
  91. package/dist/producer/producer.js +131 -0
  92. package/{src/types.ts → dist/types.d.ts} +4 -4
  93. package/dist/types.js +2 -0
  94. package/{src/utils/api.ts → dist/utils/api.d.ts} +2 -4
  95. package/dist/utils/api.js +5 -0
  96. package/dist/utils/crypto.d.ts +8 -0
  97. package/dist/utils/crypto.js +18 -0
  98. package/dist/utils/decoder.d.ts +30 -0
  99. package/{src/utils/decoder.ts → dist/utils/decoder.js} +41 -57
  100. package/dist/utils/delay.d.ts +1 -0
  101. package/dist/utils/delay.js +5 -0
  102. package/dist/utils/encoder.d.ts +28 -0
  103. package/{src/utils/encoder.ts → dist/utils/encoder.js} +50 -66
  104. package/dist/utils/error.d.ts +11 -0
  105. package/dist/utils/error.js +27 -0
  106. package/dist/utils/logger.d.ts +9 -0
  107. package/dist/utils/logger.js +32 -0
  108. package/dist/utils/memo.d.ts +1 -0
  109. package/{src/utils/memo.ts → dist/utils/memo.js} +7 -3
  110. package/dist/utils/murmur2.d.ts +3 -0
  111. package/dist/utils/murmur2.js +40 -0
  112. package/dist/utils/retrier.d.ts +10 -0
  113. package/dist/utils/retrier.js +22 -0
  114. package/dist/utils/tracer.d.ts +5 -0
  115. package/dist/utils/tracer.js +39 -0
  116. package/package.json +11 -2
  117. package/.github/workflows/release.yml +0 -17
  118. package/.prettierrc +0 -8
  119. package/certs/ca.crt +0 -29
  120. package/certs/ca.key +0 -52
  121. package/certs/ca.srl +0 -1
  122. package/certs/kafka.crt +0 -29
  123. package/certs/kafka.csr +0 -26
  124. package/certs/kafka.key +0 -52
  125. package/certs/kafka.keystore.jks +0 -0
  126. package/certs/kafka.truststore.jks +0 -0
  127. package/docker-compose.yml +0 -104
  128. package/examples/package-lock.json +0 -31
  129. package/examples/package.json +0 -14
  130. package/examples/src/client.ts +0 -9
  131. package/examples/src/consumer.ts +0 -18
  132. package/examples/src/create-topic.ts +0 -44
  133. package/examples/src/producer.ts +0 -24
  134. package/examples/src/replicator.ts +0 -25
  135. package/examples/src/utils/delay.ts +0 -1
  136. package/examples/src/utils/json.ts +0 -1
  137. package/examples/tsconfig.json +0 -7
  138. package/log4j.properties +0 -95
  139. package/scripts/generate-certs.sh +0 -24
  140. package/src/__snapshots__/request-handler.test.ts.snap +0 -978
  141. package/src/api/create-topics.ts +0 -78
  142. package/src/api/delete-topics.ts +0 -42
  143. package/src/api/fetch.ts +0 -143
  144. package/src/api/heartbeat.ts +0 -33
  145. package/src/api/init-producer-id.ts +0 -35
  146. package/src/api/join-group.ts +0 -67
  147. package/src/api/leave-group.ts +0 -48
  148. package/src/api/list-offsets.ts +0 -65
  149. package/src/api/offset-commit.ts +0 -67
  150. package/src/api/offset-fetch.ts +0 -74
  151. package/src/api/sasl-authenticate.ts +0 -21
  152. package/src/api/sasl-handshake.ts +0 -16
  153. package/src/api/sync-group.ts +0 -54
  154. package/src/broker.ts +0 -74
  155. package/src/client.ts +0 -47
  156. package/src/cluster.ts +0 -87
  157. package/src/connection.ts +0 -143
  158. package/src/consumer/consumer-group.ts +0 -209
  159. package/src/consumer/consumer-metadata.ts +0 -14
  160. package/src/consumer/consumer.ts +0 -231
  161. package/src/consumer/fetch-manager.ts +0 -179
  162. package/src/consumer/fetcher.ts +0 -57
  163. package/src/consumer/offset-manager.ts +0 -93
  164. package/src/consumer/processor.ts +0 -47
  165. package/src/distributors/assignments-to-replicas.test.ts +0 -43
  166. package/src/distributors/messages-to-topic-partition-leaders.test.ts +0 -32
  167. package/src/distributors/messages-to-topic-partition-leaders.ts +0 -19
  168. package/src/index.ts +0 -4
  169. package/src/metadata.ts +0 -122
  170. package/src/producer/producer.ts +0 -132
  171. package/src/utils/debug.ts +0 -9
  172. package/src/utils/delay.ts +0 -1
  173. package/src/utils/error.ts +0 -21
  174. package/src/utils/retrier.ts +0 -39
  175. package/src/utils/tracer.ts +0 -31
  176. package/tsconfig.json +0 -17
@@ -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 {};
@@ -1,16 +1,10 @@
1
- type Assignment = { [topicName: string]: number[] };
2
- type TopicPartitionReplicaIds = { [topicName: string]: { [partition: number]: number[] } };
3
- export type NodeAssignment = { [replicaId: number]: Assignment };
4
-
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.distributeAssignmentsToNodesOptimized = exports.distributeAssignmentsToNodesBalanced = void 0;
5
4
  /** From replica ids pick the one with fewest assignments to balance the load across brokers */
6
- export const distributeAssignmentsToNodesBalanced = (
7
- assignment: Assignment,
8
- topicPartitionReplicaIds: TopicPartitionReplicaIds,
9
- ) => {
5
+ const distributeAssignmentsToNodesBalanced = (assignment, topicPartitionReplicaIds) => {
10
6
  const replicaPartitions = getPartitionsByReplica(assignment, topicPartitionReplicaIds);
11
-
12
- const result: NodeAssignment = {};
13
-
7
+ const result = {};
14
8
  for (const [topicName, partitions] of Object.entries(assignment)) {
15
9
  for (const partition of partitions) {
16
10
  const replicaIds = topicPartitionReplicaIds[topicName][partition];
@@ -25,51 +19,33 @@ export const distributeAssignmentsToNodesBalanced = (
25
19
  result[replicaId][topicName].push(partition);
26
20
  }
27
21
  }
28
-
29
22
  return result;
30
23
  };
31
-
24
+ exports.distributeAssignmentsToNodesBalanced = distributeAssignmentsToNodesBalanced;
32
25
  /** Minimize the total number of replicas in the result to reduce the number of requests to different brokers */
33
- export const distributeAssignmentsToNodesOptimized = (
34
- assignment: Assignment,
35
- topicPartitionReplicaIds: TopicPartitionReplicaIds,
36
- ) => {
37
- const result: NodeAssignment = {};
38
-
39
- const sortFn = ([, partitionsA]: [string, string[]], [, partitionsB]: [string, string[]]) =>
40
- partitionsB.length - partitionsA.length;
41
-
26
+ const distributeAssignmentsToNodesOptimized = (assignment, topicPartitionReplicaIds) => {
27
+ const result = {};
28
+ const sortFn = ([, partitionsA], [, partitionsB]) => partitionsB.length - partitionsA.length;
42
29
  let replicaPartitions = getPartitionsByReplica(assignment, topicPartitionReplicaIds);
43
-
44
30
  while (replicaPartitions.length) {
45
31
  replicaPartitions.sort(sortFn);
46
-
47
- const [replicaId, partitions] = replicaPartitions.shift()!;
32
+ const [replicaId, partitions] = replicaPartitions.shift();
48
33
  if (!partitions.length) {
49
34
  continue;
50
35
  }
51
-
52
36
  result[parseInt(replicaId)] = partitions.reduce((acc, partition) => {
53
37
  const [topicName, partitionId] = partition.split(':');
54
38
  acc[topicName] ??= [];
55
39
  acc[topicName].push(parseInt(partitionId));
56
40
  return acc;
57
- }, {} as Assignment);
58
-
59
- replicaPartitions = replicaPartitions.map(
60
- ([replicaId, replicaPartitions]) =>
61
- [replicaId, replicaPartitions.filter((partition) => !partitions.includes(partition))] as [
62
- string,
63
- string[],
64
- ],
65
- );
41
+ }, {});
42
+ replicaPartitions = replicaPartitions.map(([replicaId, replicaPartitions]) => [replicaId, replicaPartitions.filter((partition) => !partitions.includes(partition))]);
66
43
  }
67
-
68
44
  return result;
69
45
  };
70
-
71
- const getPartitionsByReplica = (assignment: Assignment, topicPartitionReplicaIds: TopicPartitionReplicaIds) => {
72
- const partitionsByReplicaId: { [replicaId: number]: string[] } = {};
46
+ exports.distributeAssignmentsToNodesOptimized = distributeAssignmentsToNodesOptimized;
47
+ const getPartitionsByReplica = (assignment, topicPartitionReplicaIds) => {
48
+ const partitionsByReplicaId = {};
73
49
  for (const [topicName, partitions] of Object.entries(assignment)) {
74
50
  for (const partition of partitions) {
75
51
  const replicaIds = topicPartitionReplicaIds[topicName][partition];
@@ -81,5 +57,3 @@ const getPartitionsByReplica = (assignment: Assignment, topicPartitionReplicaIds
81
57
  }
82
58
  return Object.entries(partitionsByReplicaId);
83
59
  };
84
-
85
- export const distributeAssignmentsToNodes = distributeAssignmentsToNodesBalanced;
@@ -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';