wsp-ms-core 1.1.29 → 1.1.31

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.
package/dist/index.cjs CHANGED
@@ -1803,18 +1803,44 @@ var KafkaManager = class extends EventManager {
1803
1803
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1804
1804
  this.runtimeProcess = globalThis?.process;
1805
1805
  this.producerConnected = false;
1806
+ this.producerConnectPromise = null;
1806
1807
  this.isRecoveringLeader = false;
1807
1808
  this.seedBrokers = this.resolveBrokerList(this._connection.brokers ?? []);
1808
- this.groupId = this.runtimeProcess?.env?.KAFKA_GROUP || "default";
1809
+ const envGroupId = this.runtimeProcess?.env?.KAFKA_GROUP;
1810
+ this.hasExplicitGroupId = Boolean(envGroupId);
1811
+ this.groupId = envGroupId || "__missing-kafka-group__";
1812
+ if (!this.hasExplicitGroupId) {
1813
+ console.warn("[KafkaManager] KAFKA_GROUP is not set. Producing is allowed, but consuming will fail until a unique group id is configured.");
1814
+ }
1809
1815
  this.initializeKafkaClients(this.seedBrokers);
1810
1816
  this.leaderSelectionPromise = this.pinToLeaderBroker();
1811
1817
  }
1818
+ // La conexión se comparte entre sends concurrentes vía un latch: sin él, dos send() en paralelo
1819
+ // dispararían dos connect() simultáneos sobre el mismo producer. El producer queda conectado
1820
+ // entre sends (kafkajs está diseñado para conexiones long-lived); solo stop() lo desconecta.
1821
+ // Los guards `this.producer === producer` protegen contra initializeKafkaClients() reemplazando
1822
+ // el producer a mitad de un connect (recovery de líder): sin ellos, el latch viejo marcaría como
1823
+ // conectado al producer nuevo sin estarlo, y todos los sends siguientes fallarían.
1812
1824
  async ensureProducerConnected() {
1813
1825
  await this.waitForLeaderSelection();
1814
1826
  if (this.producerConnected)
1815
1827
  return;
1816
- await this.producer.connect();
1817
- this.producerConnected = true;
1828
+ if (!this.producerConnectPromise) {
1829
+ const producer = this.producer;
1830
+ this.producerConnectPromise = producer.connect().then(() => {
1831
+ if (this.producer === producer) {
1832
+ this.producerConnected = true;
1833
+ }
1834
+ }).finally(() => {
1835
+ if (this.producer === producer) {
1836
+ this.producerConnectPromise = null;
1837
+ }
1838
+ });
1839
+ }
1840
+ await this.producerConnectPromise;
1841
+ if (!this.producerConnected) {
1842
+ return this.ensureProducerConnected();
1843
+ }
1818
1844
  }
1819
1845
  initializeKafkaClients(brokers) {
1820
1846
  this.kafka = new import_kafkajs.Kafka({
@@ -1831,6 +1857,7 @@ var KafkaManager = class extends EventManager {
1831
1857
  this.producer = this.kafka.producer();
1832
1858
  this.brokers = brokers;
1833
1859
  this.producerConnected = false;
1860
+ this.producerConnectPromise = null;
1834
1861
  this.registerConsumerEventHandlers();
1835
1862
  }
1836
1863
  async pinToLeaderBroker() {
@@ -2021,7 +2048,22 @@ var KafkaManager = class extends EventManager {
2021
2048
  const next = (BigInt(message.offset) + 1n).toString();
2022
2049
  await this.consumer.commitOffsets([{ topic, partition, offset: next }]);
2023
2050
  }
2051
+ // Mantiene viva la sesión del consumer mientras un handler tarda más que el sessionTimeout
2052
+ // (default 30s de kafkajs). Sin esto, un handler lento hace que el broker expulse al consumer
2053
+ // a mitad de procesamiento → rebalance → el mensaje se reentrega → loop. kafkajs ya throttlea
2054
+ // heartbeat() internamente al heartbeatInterval, así que llamarlo de más no tiene costo.
2055
+ startHeartbeatLoop(heartbeat) {
2056
+ const interval = setInterval(() => {
2057
+ heartbeat().catch((error) => {
2058
+ console.warn("[KafkaManager] Heartbeat failed during message processing", { error: error?.message ?? error });
2059
+ });
2060
+ }, 5e3);
2061
+ return () => clearInterval(interval);
2062
+ }
2024
2063
  async run(autocommit = false) {
2064
+ if (!this.hasExplicitGroupId) {
2065
+ throw new InternalError(ErrorManager.APP_ERRORS.PROCESS, "KAFKA_GROUP env var is required to consume. Set a unique consumer group id per service.");
2066
+ }
2025
2067
  const __run = async () => {
2026
2068
  await this.waitForLeaderSelection();
2027
2069
  await this.execCallback(this._onStart, { message: `--- ${this.constructor.name} started ---` });
@@ -2069,7 +2111,12 @@ var KafkaManager = class extends EventManager {
2069
2111
  console.log("[KafkaManager] H - before execRoute", {
2070
2112
  offset: message.offset
2071
2113
  });
2072
- await this.execRoute(topic, event);
2114
+ const stopHeartbeatLoop = this.startHeartbeatLoop(heartbeat);
2115
+ try {
2116
+ await this.execRoute(topic, event);
2117
+ } finally {
2118
+ stopHeartbeatLoop();
2119
+ }
2073
2120
  console.log("[KafkaManager] I - after execRoute", {
2074
2121
  elapsedMs: Date.now() - execRouteStart
2075
2122
  });
@@ -2137,8 +2184,6 @@ var KafkaManager = class extends EventManager {
2137
2184
  }
2138
2185
  ]
2139
2186
  });
2140
- await this.producer.disconnect();
2141
- this.producerConnected = false;
2142
2187
  } catch (error) {
2143
2188
  throw new InternalError(error.toString());
2144
2189
  }
@@ -2155,6 +2200,9 @@ var KafkaManager = class extends EventManager {
2155
2200
  async stop() {
2156
2201
  await this.consumer.stop();
2157
2202
  await this.consumer.disconnect();
2203
+ if (this.producerConnectPromise) {
2204
+ await this.producerConnectPromise.catch(() => void 0);
2205
+ }
2158
2206
  if (this.producerConnected) {
2159
2207
  await this.producer.disconnect();
2160
2208
  this.producerConnected = false;
@@ -2186,7 +2234,12 @@ var _MysqlConnector = class _MysqlConnector {
2186
2234
  }
2187
2235
  async getConnection() {
2188
2236
  console.log("[MysqlConnector] BEFORE pool.getConnection");
2189
- const conn = await this._pool.getConnection();
2237
+ const conn = await Promise.race([
2238
+ this._pool.getConnection(),
2239
+ new Promise(
2240
+ (_, reject) => setTimeout(() => reject(new Error("pool.getConnection timeout")), 1e4)
2241
+ )
2242
+ ]);
2190
2243
  console.log("[MysqlConnector] AFTER pool.getConnection");
2191
2244
  return this.wrap(conn);
2192
2245
  }
@@ -2366,6 +2419,7 @@ var DefaultMysqlInboxRunner = class {
2366
2419
  }
2367
2420
  async saveEvent(e, repository) {
2368
2421
  try {
2422
+ console.log("[InboxRunner] BEFORE create inbox record");
2369
2423
  let record = new InboxRecord(
2370
2424
  UUID.create(),
2371
2425
  e.tenant,
@@ -2374,7 +2428,9 @@ var DefaultMysqlInboxRunner = class {
2374
2428
  JSON.stringify(e.message),
2375
2429
  ProcessStatus.PENDING
2376
2430
  );
2431
+ console.log("[InboxRunner] BEFORE repository.create");
2377
2432
  await repository.create(record);
2433
+ console.log("[InboxRunner] AFTER repository.create");
2378
2434
  } catch (error) {
2379
2435
  throw error;
2380
2436
  }
@@ -2398,10 +2454,16 @@ var DefaultMysqlInboxRunner = class {
2398
2454
  console.log("[InboxRunner] BEFORE getConnection");
2399
2455
  const connection = await this.databaseConnector.getConnection();
2400
2456
  console.log("[InboxRunner] AFTER getConnection");
2401
- const repository = new InboxMysqlRepository(connection);
2402
- console.log("[InboxRunner] BEFORE saveEvent");
2403
- await this.saveEvent(e, repository);
2404
- console.log("[InboxRunner] AFTER saveEvent");
2457
+ try {
2458
+ const repository = new InboxMysqlRepository(connection);
2459
+ console.log("[InboxRunner] BEFORE saveEvent");
2460
+ await this.saveEvent(e, repository);
2461
+ console.log("[InboxRunner] AFTER saveEvent");
2462
+ } finally {
2463
+ console.log("[InboxRunner] BEFORE connection.close");
2464
+ await connection.close();
2465
+ console.log("[InboxRunner] AFTER connection.close");
2466
+ }
2405
2467
  });
2406
2468
  } catch (error) {
2407
2469
  console.log(error.toString());