UNPKG

kafka-ts

Version:

**KafkaTS** is a Apache Kafka client library for Node.js. It provides both a low-level API for communicating directly with the Apache Kafka cluster and high-level APIs for publishing and subscribing to Kafka topics.

173 lines (172 loc) 8.3 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 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; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Producer = void 0; const api_1 = require("../api"); const messages_to_topic_partition_leaders_1 = require("../distributors/messages-to-topic-partition-leaders"); const partitioner_1 = require("../distributors/partitioner"); const metadata_1 = require("../metadata"); const delay_1 = require("../utils/delay"); const error_1 = require("../utils/error"); const lock_1 = require("../utils/lock"); const logger_1 = require("../utils/logger"); const shared_1 = require("../utils/shared"); const tracer_1 = require("../utils/tracer"); const trace = (0, tracer_1.createTracer)('Producer'); class Producer { cluster; options; metadata; producerId = 0n; producerEpoch = 0; sequences = {}; partition; lock = new lock_1.Lock(); constructor(cluster, options) { this.cluster = cluster; this.options = { ...options, allowTopicAutoCreation: options.allowTopicAutoCreation ?? false, partitioner: options.partitioner ?? partitioner_1.defaultPartitioner, }; this.metadata = new metadata_1.Metadata({ cluster }); this.partition = this.options.partitioner({ metadata: this.metadata }); } async send(messages, { acks = -1 } = {}) { await this.ensureConnected(); const { allowTopicAutoCreation } = this.options; const defaultTimestamp = BigInt(Date.now()); const topics = new Set(messages.map((message) => message.topic)); await this.lock.acquire([...topics].map((topic) => `metadata:${topic}`), () => this.metadata.fetchMetadataIfNecessary({ topics, allowTopicAutoCreation })); const partitionedMessages = messages.map((message) => { message.partition = this.partition(message); return message; }); const nodeTopicPartitionMessages = (0, messages_to_topic_partition_leaders_1.distributeMessagesToTopicPartitionLeaders)(partitionedMessages, this.metadata.getTopicPartitionLeaderIds()); try { await Promise.all(Object.entries(nodeTopicPartitionMessages).map(async ([nodeId, topicPartitionMessages]) => { await this.lock.acquire([`node:${nodeId}`], async () => { const topicData = Object.entries(topicPartitionMessages).map(([topic, partitionMessages]) => ({ name: topic, partitionData: Object.entries(partitionMessages).map(([partition, messages]) => { const partitionIndex = parseInt(partition); let baseTimestamp; let maxTimestamp; messages.forEach(({ timestamp = defaultTimestamp }) => { if (!baseTimestamp || timestamp < baseTimestamp) { baseTimestamp = timestamp; } if (!maxTimestamp || timestamp > maxTimestamp) { maxTimestamp = timestamp; } }); return { index: partitionIndex, baseOffset: 0n, partitionLeaderEpoch: -1, attributes: 0, lastOffsetDelta: messages.length - 1, baseTimestamp: baseTimestamp ?? 0n, maxTimestamp: maxTimestamp ?? 0n, producerId: this.producerId, producerEpoch: 0, baseSequence: this.getSequence(topic, partitionIndex), records: messages.map((message, index) => ({ attributes: 0, timestampDelta: (message.timestamp ?? defaultTimestamp) - (baseTimestamp ?? 0n), offsetDelta: index, key: message.key ?? null, value: message.value, headers: Object.entries(message.headers ?? {}).map(([key, value]) => ({ key, value, })), })), }; }), })); await this.cluster.sendRequestToNode(parseInt(nodeId))(api_1.API.PRODUCE, { transactionalId: null, acks, timeoutMs: 30000, topicData, }); topicData.forEach(({ name, partitionData }) => { partitionData.forEach(({ index, records }) => { this.updateSequence(name, index, records.length); }); }); }); })); } catch (error) { if (error instanceof error_1.KafkaTSApiError && error.errorCode === api_1.API_ERROR.NOT_LEADER_OR_FOLLOWER) { await this.metadata.fetchMetadata({ topics, allowTopicAutoCreation }); } if (error instanceof error_1.KafkaTSApiError && error.errorCode === api_1.API_ERROR.OUT_OF_ORDER_SEQUENCE_NUMBER) { await this.initProducerId(); } logger_1.log.warn('Reconnecting producer due to an unhandled error', { error }); try { await this.cluster.disconnect(); await this.cluster.connect(); } catch (error) { logger_1.log.warn('Failed to reconnect producer', { error }); } throw error; } } async close() { await this.cluster.disconnect(); } ensureConnected = (0, shared_1.shared)(async () => { await this.cluster.ensureConnected(); if (!this.producerId) { await this.initProducerId(); } }); async initProducerId() { try { const result = await this.cluster.sendRequest(api_1.API.INIT_PRODUCER_ID, { transactionalId: null, transactionTimeoutMs: 0, producerId: this.producerId, producerEpoch: this.producerEpoch, }); this.producerId = result.producerId; this.producerEpoch = result.producerEpoch; this.sequences = {}; } catch (error) { if (error.errorCode === api_1.API_ERROR.COORDINATOR_LOAD_IN_PROGRESS) { await (0, delay_1.delay)(100); return this.initProducerId(); } throw error; } } getSequence(topic, partition) { return this.sequences[topic]?.[partition] ?? 0; } updateSequence(topic, partition, messagesCount) { this.sequences[topic] ??= {}; this.sequences[topic][partition] ??= 0; this.sequences[topic][partition] += messagesCount; } } exports.Producer = Producer; __decorate([ trace(() => ({ root: true })), __metadata("design:type", Function), __metadata("design:paramtypes", [Array, Object]), __metadata("design:returntype", Promise) ], Producer.prototype, "send", null);