wsp-ms-core 1.1.23 → 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.cjs CHANGED
@@ -1778,9 +1778,25 @@ var import_kafkajs = require("kafkajs");
1778
1778
  var KafkaManager = class extends EventManager {
1779
1779
  constructor(connection) {
1780
1780
  super(connection);
1781
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1782
+ this.runtimeProcess = globalThis?.process;
1781
1783
  this.producerConnected = false;
1784
+ this.isRecoveringLeader = false;
1785
+ this.seedBrokers = this.resolveBrokerList(this._connection.brokers ?? []);
1786
+ this.groupId = this.runtimeProcess?.env?.KAFKA_GROUP || "default";
1787
+ this.initializeKafkaClients(this.seedBrokers);
1788
+ this.leaderSelectionPromise = this.pinToLeaderBroker();
1789
+ }
1790
+ async ensureProducerConnected() {
1791
+ await this.waitForLeaderSelection();
1792
+ if (this.producerConnected)
1793
+ return;
1794
+ await this.producer.connect();
1795
+ this.producerConnected = true;
1796
+ }
1797
+ initializeKafkaClients(brokers) {
1782
1798
  this.kafka = new import_kafkajs.Kafka({
1783
- brokers: this._connection.brokers,
1799
+ brokers,
1784
1800
  ssl: true,
1785
1801
  sasl: {
1786
1802
  mechanism: "scram-sha-256",
@@ -1789,14 +1805,195 @@ var KafkaManager = class extends EventManager {
1789
1805
  },
1790
1806
  logLevel: import_kafkajs.logLevel.ERROR
1791
1807
  });
1792
- this.consumer = this.kafka.consumer({ groupId: process.env.KAFKA_GROUP || "default" });
1808
+ this.consumer = this.kafka.consumer({ groupId: this.groupId });
1793
1809
  this.producer = this.kafka.producer();
1810
+ this.brokers = brokers;
1811
+ this.producerConnected = false;
1812
+ this.registerConsumerEventHandlers();
1794
1813
  }
1795
- async ensureProducerConnected() {
1796
- if (this.producerConnected)
1814
+ async pinToLeaderBroker() {
1815
+ try {
1816
+ const leaderBroker = await this.fetchLeaderBroker(this.kafka);
1817
+ if (!leaderBroker) {
1818
+ console.warn("[KafkaManager] Unable to determine leader broker. Using configured list.", { brokers: this.brokers });
1819
+ return;
1820
+ }
1821
+ if (this.brokers.length === 1 && this.brokers[0] === leaderBroker) {
1822
+ return;
1823
+ }
1824
+ console.info("[KafkaManager] Leader broker resolved. Reinitializing clients", { leaderBroker });
1825
+ this.initializeKafkaClients([leaderBroker]);
1826
+ } catch (error) {
1827
+ console.warn("[KafkaManager] Error resolving leader broker. Falling back to configured brokers.", error);
1828
+ }
1829
+ }
1830
+ buildKafkaClient(brokers) {
1831
+ return new import_kafkajs.Kafka({
1832
+ brokers,
1833
+ ssl: true,
1834
+ sasl: {
1835
+ mechanism: "scram-sha-256",
1836
+ username: this._connection.userName,
1837
+ password: this._connection.password
1838
+ },
1839
+ logLevel: import_kafkajs.logLevel.ERROR
1840
+ });
1841
+ }
1842
+ async fetchLeaderBroker(kafkaClient) {
1843
+ const admin = kafkaClient.admin();
1844
+ try {
1845
+ await admin.connect();
1846
+ const cluster = await admin.describeCluster();
1847
+ const controllerId = cluster?.controller;
1848
+ const controller = controllerId !== void 0 ? cluster?.brokers?.find((node) => node.nodeId === controllerId) : void 0;
1849
+ if (!controller?.host) {
1850
+ return null;
1851
+ }
1852
+ const host = controller.host;
1853
+ const port = controller.port ?? 9092;
1854
+ return this.normalizeBroker(`${host}:${port}`);
1855
+ } finally {
1856
+ await admin.disconnect().catch(() => void 0);
1857
+ }
1858
+ }
1859
+ async recoverFromLeaderChange(origin) {
1860
+ if (this.isRecoveringLeader)
1797
1861
  return;
1798
- await this.producer.connect();
1799
- this.producerConnected = true;
1862
+ this.isRecoveringLeader = true;
1863
+ console.warn("[KafkaManager] Recovering from leader change", { origin, brokers: this.brokers });
1864
+ await this.execCallback(this._onReconnect, {
1865
+ message: "[KafkaManager] Leader change detected. Reconnecting to new leader.",
1866
+ context: { origin }
1867
+ });
1868
+ try {
1869
+ await this.stop().catch((error) => {
1870
+ console.warn("[KafkaManager] Error stopping previous connection during recovery", error);
1871
+ });
1872
+ const seedClient = this.buildKafkaClient(this.seedBrokers);
1873
+ const leaderBroker = await this.fetchLeaderBroker(seedClient).catch((error) => {
1874
+ console.warn("[KafkaManager] Error resolving new leader broker during recovery", error);
1875
+ return null;
1876
+ });
1877
+ const brokersToUse = leaderBroker ? [leaderBroker] : this.seedBrokers;
1878
+ console.info("[KafkaManager] Reconnecting using brokers", { brokersToUse });
1879
+ this.initializeKafkaClients(brokersToUse);
1880
+ this.leaderSelectionPromise = Promise.resolve();
1881
+ await this.run();
1882
+ } catch (error) {
1883
+ console.error("[KafkaManager] Failed to recover from leader change", { origin, error });
1884
+ await this.execCallback(this._onError, error);
1885
+ } finally {
1886
+ this.isRecoveringLeader = false;
1887
+ }
1888
+ }
1889
+ async waitForLeaderSelection() {
1890
+ try {
1891
+ await this.leaderSelectionPromise;
1892
+ } catch {
1893
+ }
1894
+ }
1895
+ resolveBrokerList(brokers) {
1896
+ const envBrokers = (this.runtimeProcess?.env?.KAFKA_BROKERS ?? "").split(",").map((value) => value.trim()).filter(Boolean);
1897
+ const normalized = [...brokers, ...envBrokers].map((value) => this.normalizeBroker(value)).filter((value) => Boolean(value));
1898
+ const uniqueBrokers = Array.from(new Set(normalized));
1899
+ if (!uniqueBrokers.length) {
1900
+ throw new InternalError(ErrorManager.APP_ERRORS.PROCESS, "Kafka brokers configuration is empty.");
1901
+ }
1902
+ console.info("[KafkaManager] Using brokers", uniqueBrokers);
1903
+ return uniqueBrokers;
1904
+ }
1905
+ normalizeBroker(broker) {
1906
+ if (!broker)
1907
+ return null;
1908
+ const trimmed = broker.trim();
1909
+ if (!trimmed)
1910
+ return null;
1911
+ if (trimmed.startsWith("PLAINTEXT://") || trimmed.startsWith("SSL://")) {
1912
+ return trimmed;
1913
+ }
1914
+ return trimmed.includes(":") ? trimmed : `${trimmed}:9092`;
1915
+ }
1916
+ registerConsumerEventHandlers() {
1917
+ const events = this.consumer.events;
1918
+ this.consumer.on(events.REBALANCING, async (event) => {
1919
+ await this.logConsumerEvent("REBALANCING", event);
1920
+ });
1921
+ const rebalancedEvent = this.consumer.events.REBALANCED;
1922
+ if (rebalancedEvent) {
1923
+ this.consumer.on(rebalancedEvent, async (event) => {
1924
+ await this.logConsumerEvent("REBALANCED", event);
1925
+ });
1926
+ }
1927
+ this.consumer.on(events.REQUEST_TIMEOUT, async (event) => {
1928
+ await this.logConsumerEvent("REQUEST_TIMEOUT", event, "error");
1929
+ this.handleKafkaError(event?.payload?.error, "consumer.REQUEST_TIMEOUT", { broker: event?.payload?.broker });
1930
+ });
1931
+ this.consumer.on(events.CRASH, async (event) => {
1932
+ await this.logConsumerEvent("CRASH", event, "error");
1933
+ this.handleKafkaError(event?.payload?.error, "consumer.CRASH", { broker: event?.payload?.broker });
1934
+ if (event?.payload?.restart === false) {
1935
+ void this.recoverFromLeaderChange("consumer.CRASH.no-restart");
1936
+ }
1937
+ });
1938
+ this.consumer.on(events.DISCONNECT, async (event) => {
1939
+ await this.logConsumerEvent("DISCONNECT", event, "error");
1940
+ });
1941
+ this.consumer.on(events.CONNECT, async (event) => {
1942
+ await this.logConsumerEvent("CONNECT", event);
1943
+ });
1944
+ }
1945
+ async logConsumerEvent(eventName, kafkaEvent, level = "info") {
1946
+ const payload = kafkaEvent?.payload ?? {};
1947
+ const broker = payload?.broker ?? payload?.coordinator ?? payload?.leaderId ?? null;
1948
+ const error = payload?.error ? {
1949
+ type: payload.error.type ?? payload.error.name,
1950
+ message: payload.error.message
1951
+ } : void 0;
1952
+ const context = {
1953
+ event: eventName,
1954
+ timestamp: kafkaEvent?.timestamp ?? DateTime.now().value,
1955
+ broker,
1956
+ brokers: this.brokers,
1957
+ groupId: payload?.groupId,
1958
+ memberId: payload?.memberId,
1959
+ leaderId: payload?.leaderId,
1960
+ clientId: payload?.clientId,
1961
+ topicPartitions: payload?.topicPartitions,
1962
+ error
1963
+ };
1964
+ const message = `[KafkaManager][${eventName}] ${error?.message ?? ""}`.trim();
1965
+ if (level === "error") {
1966
+ console.error(message, context);
1967
+ await this.execCallback(this._onError, { message, context });
1968
+ } else {
1969
+ console.info(message, context);
1970
+ await this.execCallback(this._onReconnect, { message, context });
1971
+ }
1972
+ }
1973
+ handleKafkaError(error, origin, metadata = {}) {
1974
+ if (!error)
1975
+ return;
1976
+ const errorType = error.type ?? error.name;
1977
+ const isNotLeaderError = errorType === "NOT_LEADER_FOR_PARTITION" || error?.name === "KafkaJSNotLeaderForPartition";
1978
+ if (isNotLeaderError) {
1979
+ const payload = {
1980
+ origin,
1981
+ broker: error?.broker ?? metadata?.broker,
1982
+ topic: error?.topic,
1983
+ partition: error?.partition,
1984
+ metadata
1985
+ };
1986
+ console.error("[KafkaManager][NOT_LEADER_FOR_PARTITION] Reconnecting to new leader", payload);
1987
+ void this.execCallback(this._onCrash, {
1988
+ message: "[KafkaManager] NOT_LEADER_FOR_PARTITION detected. Reconnecting to new leader.",
1989
+ context: payload
1990
+ });
1991
+ void this.recoverFromLeaderChange(origin);
1992
+ return;
1993
+ }
1994
+ if (error instanceof import_kafkajs.KafkaJSRequestTimeoutError || errorType === "REQUEST_TIMEOUT") {
1995
+ console.warn("[KafkaManager][REQUEST_TIMEOUT]", { origin, metadata });
1996
+ }
1800
1997
  }
1801
1998
  async commitMessage(topic, partition, message) {
1802
1999
  const next = (BigInt(message.offset) + 1n).toString();
@@ -1804,9 +2001,10 @@ var KafkaManager = class extends EventManager {
1804
2001
  }
1805
2002
  async run(autocommit = false) {
1806
2003
  const __run = async () => {
2004
+ await this.waitForLeaderSelection();
1807
2005
  await this.execCallback(this._onStart, { message: `--- ${this.constructor.name} started ---` });
1808
2006
  await this.consumer.connect();
1809
- await this.execCallback(this._onConnected, { message: `--- ${this.constructor.name} connected to ${this._connection.brokers} ---` });
2007
+ await this.execCallback(this._onConnected, { message: `--- ${this.constructor.name} connected to ${this.brokers.join(", ")} ---` });
1810
2008
  for (let topic of this._topics) {
1811
2009
  try {
1812
2010
  await this.consumer.subscribe({ topic, fromBeginning: false });
@@ -1843,6 +2041,7 @@ var KafkaManager = class extends EventManager {
1843
2041
  await heartbeat();
1844
2042
  } catch (error) {
1845
2043
  await this.execCallback(this._onError, error);
2044
+ this.handleKafkaError(error, "consumer.eachMessage", { topic, partition });
1846
2045
  throw error;
1847
2046
  }
1848
2047
  }
@@ -1855,6 +2054,7 @@ var KafkaManager = class extends EventManager {
1855
2054
  }
1856
2055
  }
1857
2056
  async send(e) {
2057
+ await this.waitForLeaderSelection();
1858
2058
  const evt = {
1859
2059
  date: DateTime.now().value,
1860
2060
  tenant: e.tenant.value,
@@ -1883,9 +2083,6 @@ var KafkaManager = class extends EventManager {
1883
2083
  }
1884
2084
  }
1885
2085
  async start(autocommit = false) {
1886
- this.consumer.on(this.consumer.events.CRASH, async (error) => {
1887
- await this.execCallback(this._onError, new InternalError(ErrorManager.APP_ERRORS.PROCESS, error.payload.error.stack));
1888
- });
1889
2086
  await this.run();
1890
2087
  }
1891
2088
  async pause() {