wsp-ms-core 1.1.28 → 1.1.30

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;
@@ -2185,7 +2233,9 @@ var _MysqlConnector = class _MysqlConnector {
2185
2233
  return rows;
2186
2234
  }
2187
2235
  async getConnection() {
2236
+ console.log("[MysqlConnector] BEFORE pool.getConnection");
2188
2237
  const conn = await this._pool.getConnection();
2238
+ console.log("[MysqlConnector] AFTER pool.getConnection");
2189
2239
  return this.wrap(conn);
2190
2240
  }
2191
2241
  async closePool() {
@@ -2364,6 +2414,7 @@ var DefaultMysqlInboxRunner = class {
2364
2414
  }
2365
2415
  async saveEvent(e, repository) {
2366
2416
  try {
2417
+ console.log("[InboxRunner] BEFORE create inbox record");
2367
2418
  let record = new InboxRecord(
2368
2419
  UUID.create(),
2369
2420
  e.tenant,
@@ -2372,7 +2423,9 @@ var DefaultMysqlInboxRunner = class {
2372
2423
  JSON.stringify(e.message),
2373
2424
  ProcessStatus.PENDING
2374
2425
  );
2426
+ console.log("[InboxRunner] BEFORE repository.create");
2375
2427
  await repository.create(record);
2428
+ console.log("[InboxRunner] AFTER repository.create");
2376
2429
  } catch (error) {
2377
2430
  throw error;
2378
2431
  }