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 +207 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +20 -3
- package/dist/index.d.ts +20 -3
- package/dist/index.js +212 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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:
|
|
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
|
|
1796
|
-
|
|
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
|
-
|
|
1799
|
-
this.
|
|
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.
|
|
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() {
|