wsp-ms-core 1.1.24 → 1.1.26

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.d.mts CHANGED
@@ -641,14 +641,23 @@ declare class HttpNotFoundController implements HttpController {
641
641
 
642
642
  declare class KafkaManager extends EventManager {
643
643
  private readonly runtimeProcess;
644
- private readonly kafka;
645
- private readonly consumer;
646
- private readonly producer;
647
- private readonly brokers;
644
+ private kafka;
645
+ private consumer;
646
+ private producer;
647
+ private brokers;
648
+ private readonly seedBrokers;
649
+ private readonly groupId;
650
+ private leaderSelectionPromise;
648
651
  private producerConnected;
649
- private exitingDueToLeaderError;
652
+ private isRecoveringLeader;
650
653
  constructor(connection: EventManagerConnection);
651
654
  private ensureProducerConnected;
655
+ private initializeKafkaClients;
656
+ private pinToLeaderBroker;
657
+ private buildKafkaClient;
658
+ private fetchLeaderBroker;
659
+ private recoverFromLeaderChange;
660
+ private waitForLeaderSelection;
652
661
  private resolveBrokerList;
653
662
  private normalizeBroker;
654
663
  private registerConsumerEventHandlers;
package/dist/index.d.ts CHANGED
@@ -641,14 +641,23 @@ declare class HttpNotFoundController implements HttpController {
641
641
 
642
642
  declare class KafkaManager extends EventManager {
643
643
  private readonly runtimeProcess;
644
- private readonly kafka;
645
- private readonly consumer;
646
- private readonly producer;
647
- private readonly brokers;
644
+ private kafka;
645
+ private consumer;
646
+ private producer;
647
+ private brokers;
648
+ private readonly seedBrokers;
649
+ private readonly groupId;
650
+ private leaderSelectionPromise;
648
651
  private producerConnected;
649
- private exitingDueToLeaderError;
652
+ private isRecoveringLeader;
650
653
  constructor(connection: EventManagerConnection);
651
654
  private ensureProducerConnected;
655
+ private initializeKafkaClients;
656
+ private pinToLeaderBroker;
657
+ private buildKafkaClient;
658
+ private fetchLeaderBroker;
659
+ private recoverFromLeaderChange;
660
+ private waitForLeaderSelection;
652
661
  private resolveBrokerList;
653
662
  private normalizeBroker;
654
663
  private registerConsumerEventHandlers;
package/dist/index.js CHANGED
@@ -1709,10 +1709,22 @@ var KafkaManager = class extends EventManager {
1709
1709
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1710
1710
  this.runtimeProcess = globalThis?.process;
1711
1711
  this.producerConnected = false;
1712
- this.exitingDueToLeaderError = false;
1713
- this.brokers = this.resolveBrokerList(this._connection.brokers ?? []);
1712
+ this.isRecoveringLeader = false;
1713
+ this.seedBrokers = this.resolveBrokerList(this._connection.brokers ?? []);
1714
+ this.groupId = this.runtimeProcess?.env?.KAFKA_GROUP || "default";
1715
+ this.initializeKafkaClients(this.seedBrokers);
1716
+ this.leaderSelectionPromise = this.pinToLeaderBroker();
1717
+ }
1718
+ async ensureProducerConnected() {
1719
+ await this.waitForLeaderSelection();
1720
+ if (this.producerConnected)
1721
+ return;
1722
+ await this.producer.connect();
1723
+ this.producerConnected = true;
1724
+ }
1725
+ initializeKafkaClients(brokers) {
1714
1726
  this.kafka = new Kafka({
1715
- brokers: this.brokers,
1727
+ brokers,
1716
1728
  ssl: true,
1717
1729
  sasl: {
1718
1730
  mechanism: "scram-sha-256",
@@ -1721,16 +1733,92 @@ var KafkaManager = class extends EventManager {
1721
1733
  },
1722
1734
  logLevel: logLevel.ERROR
1723
1735
  });
1724
- const groupId = this.runtimeProcess?.env?.KAFKA_GROUP || "default";
1725
- this.consumer = this.kafka.consumer({ groupId });
1736
+ this.consumer = this.kafka.consumer({ groupId: this.groupId });
1726
1737
  this.producer = this.kafka.producer();
1738
+ this.brokers = brokers;
1739
+ this.producerConnected = false;
1727
1740
  this.registerConsumerEventHandlers();
1728
1741
  }
1729
- async ensureProducerConnected() {
1730
- if (this.producerConnected)
1742
+ async pinToLeaderBroker() {
1743
+ try {
1744
+ const leaderBroker = await this.fetchLeaderBroker(this.kafka);
1745
+ if (!leaderBroker) {
1746
+ console.warn("[KafkaManager] Unable to determine leader broker. Using configured list.", { brokers: this.brokers });
1747
+ return;
1748
+ }
1749
+ if (this.brokers.length === 1 && this.brokers[0] === leaderBroker) {
1750
+ return;
1751
+ }
1752
+ console.info("[KafkaManager] Leader broker resolved. Reinitializing clients", { leaderBroker });
1753
+ this.initializeKafkaClients([leaderBroker]);
1754
+ } catch (error) {
1755
+ console.warn("[KafkaManager] Error resolving leader broker. Falling back to configured brokers.", error);
1756
+ }
1757
+ }
1758
+ buildKafkaClient(brokers) {
1759
+ return new Kafka({
1760
+ brokers,
1761
+ ssl: true,
1762
+ sasl: {
1763
+ mechanism: "scram-sha-256",
1764
+ username: this._connection.userName,
1765
+ password: this._connection.password
1766
+ },
1767
+ logLevel: logLevel.ERROR
1768
+ });
1769
+ }
1770
+ async fetchLeaderBroker(kafkaClient) {
1771
+ const admin = kafkaClient.admin();
1772
+ try {
1773
+ await admin.connect();
1774
+ const cluster = await admin.describeCluster();
1775
+ const controllerId = cluster?.controller;
1776
+ const controller = controllerId !== void 0 ? cluster?.brokers?.find((node) => node.nodeId === controllerId) : void 0;
1777
+ if (!controller?.host) {
1778
+ return null;
1779
+ }
1780
+ const host = controller.host;
1781
+ const port = controller.port ?? 9092;
1782
+ return this.normalizeBroker(`${host}:${port}`);
1783
+ } finally {
1784
+ await admin.disconnect().catch(() => void 0);
1785
+ }
1786
+ }
1787
+ async recoverFromLeaderChange(origin) {
1788
+ if (this.isRecoveringLeader)
1731
1789
  return;
1732
- await this.producer.connect();
1733
- this.producerConnected = true;
1790
+ this.isRecoveringLeader = true;
1791
+ console.warn("[KafkaManager] Recovering from leader change", { origin, brokers: this.brokers });
1792
+ await this.execCallback(this._onReconnect, {
1793
+ message: "[KafkaManager] Leader change detected. Reconnecting to new leader.",
1794
+ context: { origin }
1795
+ });
1796
+ try {
1797
+ await this.stop().catch((error) => {
1798
+ console.warn("[KafkaManager] Error stopping previous connection during recovery", error);
1799
+ });
1800
+ const seedClient = this.buildKafkaClient(this.seedBrokers);
1801
+ const leaderBroker = await this.fetchLeaderBroker(seedClient).catch((error) => {
1802
+ console.warn("[KafkaManager] Error resolving new leader broker during recovery", error);
1803
+ return null;
1804
+ });
1805
+ const brokersToUse = leaderBroker ? [leaderBroker] : this.seedBrokers;
1806
+ console.info("[KafkaManager] Reconnecting using brokers", { brokersToUse });
1807
+ this.initializeKafkaClients(brokersToUse);
1808
+ this.leaderSelectionPromise = Promise.resolve();
1809
+ await this.run();
1810
+ } catch (error) {
1811
+ console.error("[KafkaManager] Failed to recover from leader change", { origin, error });
1812
+ await this.execCallback(this._onError, error);
1813
+ } finally {
1814
+ this.isRecoveringLeader = false;
1815
+ }
1816
+ }
1817
+ async waitForLeaderSelection() {
1818
+ try {
1819
+ await this.leaderSelectionPromise;
1820
+ } catch {
1821
+ }
1734
1822
  }
1735
1823
  resolveBrokerList(brokers) {
1736
1824
  const envBrokers = (this.runtimeProcess?.env?.KAFKA_BROKERS ?? "").split(",").map((value) => value.trim()).filter(Boolean);
@@ -1771,6 +1859,9 @@ var KafkaManager = class extends EventManager {
1771
1859
  this.consumer.on(events.CRASH, async (event) => {
1772
1860
  await this.logConsumerEvent("CRASH", event, "error");
1773
1861
  this.handleKafkaError(event?.payload?.error, "consumer.CRASH", { broker: event?.payload?.broker });
1862
+ if (event?.payload?.restart === false) {
1863
+ void this.recoverFromLeaderChange("consumer.CRASH.no-restart");
1864
+ }
1774
1865
  });
1775
1866
  this.consumer.on(events.DISCONNECT, async (event) => {
1776
1867
  await this.logConsumerEvent("DISCONNECT", event, "error");
@@ -1812,8 +1903,7 @@ var KafkaManager = class extends EventManager {
1812
1903
  return;
1813
1904
  const errorType = error.type ?? error.name;
1814
1905
  const isNotLeaderError = errorType === "NOT_LEADER_FOR_PARTITION" || error?.name === "KafkaJSNotLeaderForPartition";
1815
- if (!this.exitingDueToLeaderError && isNotLeaderError) {
1816
- this.exitingDueToLeaderError = true;
1906
+ if (isNotLeaderError) {
1817
1907
  const payload = {
1818
1908
  origin,
1819
1909
  broker: error?.broker ?? metadata?.broker,
@@ -1821,12 +1911,12 @@ var KafkaManager = class extends EventManager {
1821
1911
  partition: error?.partition,
1822
1912
  metadata
1823
1913
  };
1824
- console.error("[KafkaManager][NOT_LEADER_FOR_PARTITION] Forcing shutdown", payload);
1914
+ console.error("[KafkaManager][NOT_LEADER_FOR_PARTITION] Reconnecting to new leader", payload);
1825
1915
  void this.execCallback(this._onCrash, {
1826
- message: "[KafkaManager] NOT_LEADER_FOR_PARTITION detected. Forcing shutdown.",
1916
+ message: "[KafkaManager] NOT_LEADER_FOR_PARTITION detected. Reconnecting to new leader.",
1827
1917
  context: payload
1828
1918
  });
1829
- setTimeout(() => this.runtimeProcess?.exit?.(1), 0);
1919
+ void this.recoverFromLeaderChange(origin);
1830
1920
  return;
1831
1921
  }
1832
1922
  if (error instanceof KafkaJSRequestTimeoutError || errorType === "REQUEST_TIMEOUT") {
@@ -1839,6 +1929,7 @@ var KafkaManager = class extends EventManager {
1839
1929
  }
1840
1930
  async run(autocommit = false) {
1841
1931
  const __run = async () => {
1932
+ await this.waitForLeaderSelection();
1842
1933
  await this.execCallback(this._onStart, { message: `--- ${this.constructor.name} started ---` });
1843
1934
  await this.consumer.connect();
1844
1935
  await this.execCallback(this._onConnected, { message: `--- ${this.constructor.name} connected to ${this.brokers.join(", ")} ---` });
@@ -1891,6 +1982,7 @@ var KafkaManager = class extends EventManager {
1891
1982
  }
1892
1983
  }
1893
1984
  async send(e) {
1985
+ await this.waitForLeaderSelection();
1894
1986
  const evt = {
1895
1987
  date: DateTime.now().value,
1896
1988
  tenant: e.tenant.value,