@514labs/moose-lib 0.6.276-ci-6-g278c5539 → 0.6.276-ci-1-gfe86cd2c

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.
@@ -13,7 +13,11 @@ var __esm = (fn, res) => function __init() {
13
13
  // src/commons.ts
14
14
  import http from "http";
15
15
  import { createClient } from "@clickhouse/client";
16
- import { KafkaJS } from "@confluentinc/kafka-javascript";
16
+ import {
17
+ KafkaConsumer,
18
+ KafkaJS,
19
+ CODES
20
+ } from "@confluentinc/kafka-javascript";
17
21
  function isTruthy(value) {
18
22
  if (!value) return false;
19
23
  switch (value.trim().toLowerCase()) {
@@ -42,7 +46,7 @@ function createProducerConfig(maxMessageBytes) {
42
46
  ...maxMessageBytes && { "message.max.bytes": maxMessageBytes }
43
47
  };
44
48
  }
45
- var Kafka, compilerLog, getClickhouseClient, cliLog, MAX_RETRIES, MAX_RETRY_TIME_MS, RETRY_INITIAL_TIME_MS, MAX_RETRIES_PRODUCER, ACKs, parseBrokerString, logError, buildSaslConfig, getKafkaClient;
49
+ var Kafka, compilerLog, getClickhouseClient, cliLog, MAX_RETRIES, MAX_RETRY_TIME_MS, RETRY_INITIAL_TIME_MS, MAX_RETRIES_PRODUCER, ACKs, parseBrokerString, logError, buildSaslConfig, getKafkaClient, buildNativeSaslConfig, createNativeKafkaConsumer;
46
50
  var init_commons = __esm({
47
51
  "src/commons.ts"() {
48
52
  "use strict";
@@ -136,6 +140,59 @@ var init_commons = __esm({
136
140
  }
137
141
  });
138
142
  };
143
+ buildNativeSaslConfig = (logger2, cfg) => {
144
+ if (!cfg.saslMechanism || !cfg.saslUsername || !cfg.saslPassword) {
145
+ return {};
146
+ }
147
+ const mechanism = cfg.saslMechanism.toUpperCase();
148
+ const validMechanisms = ["PLAIN", "SCRAM-SHA-256", "SCRAM-SHA-512"];
149
+ if (!validMechanisms.includes(mechanism)) {
150
+ logger2.warn(`Unsupported SASL mechanism: ${cfg.saslMechanism}`);
151
+ return {};
152
+ }
153
+ return {
154
+ "sasl.mechanisms": mechanism,
155
+ "sasl.username": cfg.saslUsername,
156
+ "sasl.password": cfg.saslPassword
157
+ };
158
+ };
159
+ createNativeKafkaConsumer = (cfg, logger2, rebalanceCb) => {
160
+ const brokers = parseBrokerString(cfg.broker || "");
161
+ if (brokers.length === 0) {
162
+ throw new Error(`No valid broker addresses found in: "${cfg.broker}"`);
163
+ }
164
+ logger2.log(
165
+ `Creating native KafkaConsumer with brokers: ${brokers.join(", ")}`
166
+ );
167
+ logger2.log(`Security protocol: ${cfg.securityProtocol || "plaintext"}`);
168
+ logger2.log(`Client ID: ${cfg.clientId}`);
169
+ logger2.log(`Group ID: ${cfg.groupId}`);
170
+ const saslConfig = buildNativeSaslConfig(logger2, cfg);
171
+ const consumerConfig = {
172
+ // Connection
173
+ "bootstrap.servers": brokers.join(","),
174
+ "client.id": cfg.clientId,
175
+ // Group management
176
+ "group.id": cfg.groupId,
177
+ "session.timeout.ms": cfg.sessionTimeoutMs ?? 3e4,
178
+ "heartbeat.interval.ms": cfg.heartbeatIntervalMs ?? 3e3,
179
+ "max.poll.interval.ms": cfg.maxPollIntervalMs ?? 3e5,
180
+ // Offset management
181
+ "enable.auto.commit": cfg.autoCommit ?? true,
182
+ "auto.commit.interval.ms": cfg.autoCommitIntervalMs ?? 5e3,
183
+ // Security
184
+ ...cfg.securityProtocol === "SASL_SSL" && {
185
+ "security.protocol": "sasl_ssl"
186
+ },
187
+ ...saslConfig,
188
+ // Rebalance callback
189
+ ...rebalanceCb && { rebalance_cb: rebalanceCb }
190
+ };
191
+ const topicConfig = {
192
+ "auto.offset.reset": cfg.autoOffsetReset ?? "earliest"
193
+ };
194
+ return new KafkaConsumer(consumerConfig, topicConfig);
195
+ };
139
196
  }
140
197
  });
141
198
 
@@ -1859,15 +1916,13 @@ var runBlocks = async (config) => {
1859
1916
  // src/streaming-functions/runner.ts
1860
1917
  init_commons();
1861
1918
  import { Readable as Readable2 } from "stream";
1862
- import { KafkaJS as KafkaJS2 } from "@confluentinc/kafka-javascript";
1919
+ import { KafkaJS as KafkaJS2, CODES as CODES2 } from "@confluentinc/kafka-javascript";
1863
1920
  import { Buffer as Buffer2 } from "buffer";
1864
1921
  import * as process3 from "process";
1865
1922
  import * as http3 from "http";
1866
1923
  var { Kafka: Kafka2 } = KafkaJS2;
1867
1924
  var HOSTNAME = process3.env.HOSTNAME;
1868
1925
  var AUTO_COMMIT_INTERVAL_MS = 5e3;
1869
- var PARTITIONS_CONSUMED_CONCURRENTLY = 3;
1870
- var MAX_RETRIES_CONSUMER = 150;
1871
1926
  var SESSION_TIMEOUT_CONSUMER = 3e4;
1872
1927
  var HEARTBEAT_INTERVAL_CONSUMER = 3e3;
1873
1928
  var DEFAULT_MAX_STREAMING_CONCURRENCY = 100;
@@ -1908,23 +1963,31 @@ var stopProducer = async (logger2, producer) => {
1908
1963
  var stopConsumer = async (logger2, consumer, sourceTopic) => {
1909
1964
  try {
1910
1965
  logger2.log("Pausing consumer...");
1911
- const partitionNumbers = Array.from(
1966
+ const topicPartitions = Array.from(
1912
1967
  { length: sourceTopic.partitions },
1913
- (_, i) => i
1914
- );
1915
- await consumer.pause([
1916
- {
1968
+ (_, i) => ({
1917
1969
  topic: sourceTopic.name,
1918
- partitions: partitionNumbers
1919
- }
1920
- ]);
1970
+ partition: i
1971
+ })
1972
+ );
1973
+ consumer.pause(topicPartitions);
1921
1974
  logger2.log("Disconnecting consumer...");
1922
- await consumer.disconnect();
1975
+ await new Promise((resolve2, reject) => {
1976
+ consumer.disconnect((err) => {
1977
+ if (err) {
1978
+ reject(err);
1979
+ } else {
1980
+ resolve2();
1981
+ }
1982
+ });
1983
+ });
1923
1984
  logger2.log("Consumer is shutting down...");
1924
1985
  } catch (error) {
1925
1986
  logger2.error(`Error during consumer shutdown: ${error}`);
1926
1987
  try {
1927
- await consumer.disconnect();
1988
+ await new Promise((resolve2) => {
1989
+ consumer.disconnect(() => resolve2());
1990
+ });
1928
1991
  logger2.log("Consumer disconnected after error");
1929
1992
  } catch (disconnectError) {
1930
1993
  logger2.error(`Failed to disconnect consumer: ${disconnectError}`);
@@ -2164,7 +2227,15 @@ var startConsumer = async (args, logger2, metrics, _parallelism, consumer, produ
2164
2227
  }
2165
2228
  try {
2166
2229
  logger2.log("Connecting consumer...");
2167
- await consumer.connect();
2230
+ await new Promise((resolve2, reject) => {
2231
+ consumer.connect({}, (err) => {
2232
+ if (err) {
2233
+ reject(err);
2234
+ } else {
2235
+ resolve2();
2236
+ }
2237
+ });
2238
+ });
2168
2239
  logger2.log("Consumer connected successfully");
2169
2240
  } catch (error) {
2170
2241
  logger2.error("Failed to connect consumer:");
@@ -2189,61 +2260,94 @@ var startConsumer = async (args, logger2, metrics, _parallelism, consumer, produ
2189
2260
  streamingFunctions = [[loadStreamingFunction(args.functionFilePath), {}]];
2190
2261
  fieldMutations = void 0;
2191
2262
  }
2192
- await consumer.subscribe({
2193
- topics: [args.sourceTopic.name]
2194
- // Use full topic name for Kafka operations
2195
- });
2196
- await consumer.run({
2197
- eachBatchAutoResolve: true,
2198
- // Enable parallel processing of partitions
2199
- partitionsConsumedConcurrently: PARTITIONS_CONSUMED_CONCURRENTLY,
2200
- // To be adjusted
2201
- eachBatch: async ({ batch, heartbeat, isRunning, isStale }) => {
2202
- if (!isRunning() || isStale()) {
2203
- return;
2204
- }
2205
- metrics.count_in += batch.messages.length;
2206
- cliLog({
2207
- action: "Received",
2208
- message: `${logger2.logPrefix} ${batch.messages.length} message(s)`
2209
- });
2210
- logger2.log(`Received ${batch.messages.length} message(s)`);
2211
- let index = 0;
2212
- const readableStream = Readable2.from(batch.messages);
2213
- const processedMessages = await readableStream.map(
2214
- async (message) => {
2215
- index++;
2216
- if (batch.messages.length > DEFAULT_MAX_STREAMING_CONCURRENCY && index % DEFAULT_MAX_STREAMING_CONCURRENCY || index - 1 === batch.messages.length) {
2217
- await heartbeat();
2263
+ consumer.subscribe([args.sourceTopic.name]);
2264
+ consumer.setDefaultConsumeTimeout(1e3);
2265
+ let isRunning = true;
2266
+ const consumeLoop = async () => {
2267
+ while (isRunning && consumer.isConnected()) {
2268
+ try {
2269
+ const messages = await new Promise(
2270
+ (resolve2, reject) => {
2271
+ consumer.consume(CONSUMER_MAX_BATCH_SIZE, (err, messages2) => {
2272
+ if (err) {
2273
+ reject(err);
2274
+ } else {
2275
+ resolve2(messages2 || []);
2276
+ }
2277
+ });
2218
2278
  }
2219
- return handleMessage(
2279
+ );
2280
+ if (messages.length === 0) {
2281
+ continue;
2282
+ }
2283
+ metrics.count_in += messages.length;
2284
+ cliLog({
2285
+ action: "Received",
2286
+ message: `${logger2.logPrefix} ${messages.length} message(s)`
2287
+ });
2288
+ logger2.log(`Received ${messages.length} message(s)`);
2289
+ const readableStream = Readable2.from(messages);
2290
+ const processedMessages = await readableStream.map(
2291
+ async (message) => {
2292
+ const kafkaMessage = {
2293
+ value: message.value,
2294
+ key: message.key,
2295
+ partition: message.partition,
2296
+ offset: message.offset,
2297
+ timestamp: message.timestamp,
2298
+ headers: message.headers
2299
+ };
2300
+ return handleMessage(
2301
+ logger2,
2302
+ streamingFunctions,
2303
+ kafkaMessage,
2304
+ producer,
2305
+ fieldMutations
2306
+ );
2307
+ },
2308
+ {
2309
+ concurrency: MAX_STREAMING_CONCURRENCY
2310
+ }
2311
+ ).toArray();
2312
+ const filteredMessages = processedMessages.flat().filter((msg) => msg !== void 0 && msg.value !== void 0);
2313
+ if (args.targetTopic === void 0 || processedMessages.length === 0) {
2314
+ continue;
2315
+ }
2316
+ if (filteredMessages.length > 0) {
2317
+ await sendMessages(
2220
2318
  logger2,
2221
- streamingFunctions,
2222
- message,
2319
+ metrics,
2320
+ args.targetTopic,
2223
2321
  producer,
2224
- fieldMutations
2322
+ filteredMessages
2225
2323
  );
2226
- },
2227
- {
2228
- concurrency: MAX_STREAMING_CONCURRENCY
2229
2324
  }
2230
- ).toArray();
2231
- const filteredMessages = processedMessages.flat().filter((msg) => msg !== void 0 && msg.value !== void 0);
2232
- if (args.targetTopic === void 0 || processedMessages.length === 0) {
2233
- return;
2234
- }
2235
- await heartbeat();
2236
- if (filteredMessages.length > 0) {
2237
- await sendMessages(
2238
- logger2,
2239
- metrics,
2240
- args.targetTopic,
2241
- producer,
2242
- filteredMessages
2243
- );
2325
+ } catch (error) {
2326
+ if (error && typeof error === "object" && "code" in error) {
2327
+ const kafkaError = error;
2328
+ if (kafkaError.code === CODES2.ERRORS.ERR__TIMED_OUT) {
2329
+ continue;
2330
+ }
2331
+ if (kafkaError.code === CODES2.ERRORS.ERR__PARTITION_EOF) {
2332
+ continue;
2333
+ }
2334
+ }
2335
+ logger2.error(`Error consuming messages: ${error}`);
2336
+ if (error instanceof Error) {
2337
+ logError(logger2, error);
2338
+ }
2339
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
2244
2340
  }
2245
2341
  }
2342
+ };
2343
+ consumeLoop().catch((err) => {
2344
+ logger2.error(`Consumer loop crashed: ${err}`);
2345
+ isRunning = false;
2246
2346
  });
2347
+ consumer._isRunning = isRunning;
2348
+ consumer._stopConsuming = () => {
2349
+ isRunning = false;
2350
+ };
2247
2351
  logger2.log("Consumer is running...");
2248
2352
  };
2249
2353
  var buildLogger = (args, workerId) => {
@@ -2323,6 +2427,33 @@ var runStreamingFunctions = async (args) => {
2323
2427
  setTimeout(() => sendMessageMetrics(logger2, metrics), 1e3);
2324
2428
  const clientIdPrefix = HOSTNAME ? `${HOSTNAME}-` : "";
2325
2429
  const processId = `${clientIdPrefix}${streamingFuncId}-ts-${worker.id}`;
2430
+ const consumer = createNativeKafkaConsumer(
2431
+ {
2432
+ clientId: processId,
2433
+ broker: args.broker,
2434
+ groupId: streamingFuncId,
2435
+ securityProtocol: args.securityProtocol,
2436
+ saslUsername: args.saslUsername,
2437
+ saslPassword: args.saslPassword,
2438
+ saslMechanism: args.saslMechanism,
2439
+ sessionTimeoutMs: SESSION_TIMEOUT_CONSUMER,
2440
+ heartbeatIntervalMs: HEARTBEAT_INTERVAL_CONSUMER,
2441
+ autoCommit: true,
2442
+ autoCommitIntervalMs: AUTO_COMMIT_INTERVAL_MS,
2443
+ autoOffsetReset: "earliest",
2444
+ maxBatchSize: CONSUMER_MAX_BATCH_SIZE
2445
+ },
2446
+ logger2,
2447
+ (err, assignments) => {
2448
+ if (err.code === CODES2.ERRORS.ERR__ASSIGN_PARTITIONS) {
2449
+ logger2.log(`Assigned partitions: ${JSON.stringify(assignments)}`);
2450
+ } else if (err.code === CODES2.ERRORS.ERR__REVOKE_PARTITIONS) {
2451
+ logger2.log(`Revoked partitions: ${JSON.stringify(assignments)}`);
2452
+ } else {
2453
+ logger2.error(`Rebalance error: ${err.message}`);
2454
+ }
2455
+ }
2456
+ );
2326
2457
  const kafka = await getKafkaClient(
2327
2458
  {
2328
2459
  clientId: processId,
@@ -2334,20 +2465,6 @@ var runStreamingFunctions = async (args) => {
2334
2465
  },
2335
2466
  logger2
2336
2467
  );
2337
- const consumer = kafka.consumer({
2338
- kafkaJS: {
2339
- groupId: streamingFuncId,
2340
- sessionTimeout: SESSION_TIMEOUT_CONSUMER,
2341
- heartbeatInterval: HEARTBEAT_INTERVAL_CONSUMER,
2342
- retry: {
2343
- retries: MAX_RETRIES_CONSUMER
2344
- },
2345
- autoCommit: true,
2346
- autoCommitInterval: AUTO_COMMIT_INTERVAL_MS,
2347
- fromBeginning: true
2348
- },
2349
- "js.consumer.max.batch.size": CONSUMER_MAX_BATCH_SIZE
2350
- });
2351
2468
  const maxMessageBytes = args.targetTopic?.max_message_bytes || 1024 * 1024;
2352
2469
  const producer = kafka.producer(
2353
2470
  createProducerConfig(maxMessageBytes)
@@ -2384,6 +2501,9 @@ var runStreamingFunctions = async (args) => {
2384
2501
  },
2385
2502
  workerStop: async ([logger2, producer, consumer]) => {
2386
2503
  logger2.log(`Received SIGTERM, shutting down gracefully...`);
2504
+ if (consumer._stopConsuming) {
2505
+ consumer._stopConsuming();
2506
+ }
2387
2507
  logger2.log("Stopping consumer first...");
2388
2508
  await stopConsumer(logger2, consumer, args.sourceTopic);
2389
2509
  logger2.log("Waiting for in-flight messages to complete...");