UNPKG

kafka-pub-sub

Version:

Enterprise-grade Kafka publish/subscribe library for Node.js — producer pool, batch sending, DLQ, SSL/SASL, multi-broker, and real-world industry examples.

157 lines (142 loc) 4.8 kB
const kafka = require('./config/kafka'); const validateTopic = require('./validation/validateTopic'); /** * @typedef {Object} TopicConfig * @property {string} topic - Topic name. * @property {number} [numPartitions=6] - Number of partitions. * @property {number} [replicationFactor=1] - Replication factor (set ≥ 3 in production). * @property {Object} [configEntries] - Broker-level topic config overrides. * @property {string} [configEntries.retention.ms] - Retention in milliseconds. * @property {string} [configEntries.cleanup.policy] - 'delete' | 'compact'. * @property {string} [configEntries.compression.type] - Codec for stored messages. */ /** * Admin client wrapper for programmatic Kafka topic management. * * Typical enterprise uses: * - CI/CD pipelines that need to create topics before deploying consumers. * - Health checks that verify required topics exist. * - Tenant provisioning that creates isolated topics per customer. * * @example * const admin = KafkaAdmin(); * * await admin.createTopics([ * { topic: 'order.placed', numPartitions: 12, replicationFactor: 3 }, * { topic: 'payment.transaction', numPartitions: 24, replicationFactor: 3, * configEntries: { 'retention.ms': '604800000' } }, // 7 days * ]); * * const topics = await admin.listTopics(); * console.log(topics); // ['order.placed', 'payment.transaction', …] * * await admin.disconnect(); */ const KafkaAdmin = () => { const admin = kafka.admin(); let connected = false; const ensureConnected = async () => { if (!connected) { await admin.connect(); connected = true; } }; return { /** * Creates one or more topics. Silently skips topics that already exist. * * @param {TopicConfig[]} topics * @param {Object} [options={}] * @param {boolean} [options.waitForLeaders=true] - Wait for partition leaders before resolving. * @param {number} [options.timeout=5000] * @return {Promise<boolean>} True if at least one topic was created. */ async createTopics(topics, options = {}) { await ensureConnected(); const { waitForLeaders = true, timeout = 5000 } = options; const topicConfigs = topics.map((t) => { validateTopic(t.topic); const config = { topic: t.topic, numPartitions: t.numPartitions || 6, replicationFactor: t.replicationFactor || 1, }; if (t.configEntries) { config.configEntries = Object.entries(t.configEntries).map(([name, value]) => ({ name, value: String(value), })); } return config; }); return admin.createTopics({ topics: topicConfigs, waitForLeaders, timeout }); }, /** * Deletes one or more topics. * * @param {string[]} topics - Array of topic names to delete. * @return {Promise<void>} */ async deleteTopics(topics) { await ensureConnected(); topics.forEach(validateTopic); return admin.deleteTopics({ topics }); }, /** * Lists all topic names in the cluster. * * @return {Promise<string[]>} */ async listTopics() { await ensureConnected(); return admin.listTopics(); }, /** * Fetches metadata for one or more topics including partition and leader info. * * @param {string[]} [topics] - If omitted, returns metadata for all topics. * @return {Promise<Object>} KafkaJS metadata object. */ async getTopicMetadata(topics) { await ensureConnected(); return admin.fetchTopicMetadata({ topics }); }, /** * Returns the current consumer group offsets for a given topic. * Useful for monitoring consumer lag. * * @param {string} groupId * @param {string} topic * @return {Promise<Object>} */ async getConsumerGroupOffsets(groupId, topic) { await ensureConnected(); validateTopic(topic); return admin.fetchOffsets({ groupId, topics: [topic] }); }, /** * Checks whether all given topics exist in the cluster. * * @param {string[]} topics * @return {Promise<{ exists: boolean, missing: string[] }>} */ async topicsExist(topics) { await ensureConnected(); const existing = await admin.listTopics(); const existingSet = new Set(existing); const missing = topics.filter((t) => !existingSet.has(t)); return { exists: missing.length === 0, missing }; }, /** * Disconnect the admin client. * @return {Promise<void>} */ async disconnect() { if (connected) { await admin.disconnect(); connected = false; } }, }; }; module.exports = KafkaAdmin;