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.d.mts
CHANGED
|
@@ -640,12 +640,29 @@ declare class HttpNotFoundController implements HttpController {
|
|
|
640
640
|
}
|
|
641
641
|
|
|
642
642
|
declare class KafkaManager extends EventManager {
|
|
643
|
-
private readonly
|
|
644
|
-
private
|
|
645
|
-
private
|
|
643
|
+
private readonly runtimeProcess;
|
|
644
|
+
private kafka;
|
|
645
|
+
private consumer;
|
|
646
|
+
private producer;
|
|
647
|
+
private brokers;
|
|
648
|
+
private readonly seedBrokers;
|
|
649
|
+
private readonly groupId;
|
|
650
|
+
private leaderSelectionPromise;
|
|
646
651
|
private producerConnected;
|
|
652
|
+
private isRecoveringLeader;
|
|
647
653
|
constructor(connection: EventManagerConnection);
|
|
648
654
|
private ensureProducerConnected;
|
|
655
|
+
private initializeKafkaClients;
|
|
656
|
+
private pinToLeaderBroker;
|
|
657
|
+
private buildKafkaClient;
|
|
658
|
+
private fetchLeaderBroker;
|
|
659
|
+
private recoverFromLeaderChange;
|
|
660
|
+
private waitForLeaderSelection;
|
|
661
|
+
private resolveBrokerList;
|
|
662
|
+
private normalizeBroker;
|
|
663
|
+
private registerConsumerEventHandlers;
|
|
664
|
+
private logConsumerEvent;
|
|
665
|
+
private handleKafkaError;
|
|
649
666
|
private commitMessage;
|
|
650
667
|
private run;
|
|
651
668
|
send(e: EventMessage): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -640,12 +640,29 @@ declare class HttpNotFoundController implements HttpController {
|
|
|
640
640
|
}
|
|
641
641
|
|
|
642
642
|
declare class KafkaManager extends EventManager {
|
|
643
|
-
private readonly
|
|
644
|
-
private
|
|
645
|
-
private
|
|
643
|
+
private readonly runtimeProcess;
|
|
644
|
+
private kafka;
|
|
645
|
+
private consumer;
|
|
646
|
+
private producer;
|
|
647
|
+
private brokers;
|
|
648
|
+
private readonly seedBrokers;
|
|
649
|
+
private readonly groupId;
|
|
650
|
+
private leaderSelectionPromise;
|
|
646
651
|
private producerConnected;
|
|
652
|
+
private isRecoveringLeader;
|
|
647
653
|
constructor(connection: EventManagerConnection);
|
|
648
654
|
private ensureProducerConnected;
|
|
655
|
+
private initializeKafkaClients;
|
|
656
|
+
private pinToLeaderBroker;
|
|
657
|
+
private buildKafkaClient;
|
|
658
|
+
private fetchLeaderBroker;
|
|
659
|
+
private recoverFromLeaderChange;
|
|
660
|
+
private waitForLeaderSelection;
|
|
661
|
+
private resolveBrokerList;
|
|
662
|
+
private normalizeBroker;
|
|
663
|
+
private registerConsumerEventHandlers;
|
|
664
|
+
private logConsumerEvent;
|
|
665
|
+
private handleKafkaError;
|
|
649
666
|
private commitMessage;
|
|
650
667
|
private run;
|
|
651
668
|
send(e: EventMessage): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1698,13 +1698,33 @@ var HttpNotFoundController = class {
|
|
|
1698
1698
|
};
|
|
1699
1699
|
|
|
1700
1700
|
// src/infrastructure/kafka/KafkaManager.ts
|
|
1701
|
-
import {
|
|
1701
|
+
import {
|
|
1702
|
+
Kafka,
|
|
1703
|
+
KafkaJSRequestTimeoutError,
|
|
1704
|
+
logLevel
|
|
1705
|
+
} from "kafkajs";
|
|
1702
1706
|
var KafkaManager = class extends EventManager {
|
|
1703
1707
|
constructor(connection) {
|
|
1704
1708
|
super(connection);
|
|
1709
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1710
|
+
this.runtimeProcess = globalThis?.process;
|
|
1705
1711
|
this.producerConnected = false;
|
|
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) {
|
|
1706
1726
|
this.kafka = new Kafka({
|
|
1707
|
-
brokers
|
|
1727
|
+
brokers,
|
|
1708
1728
|
ssl: true,
|
|
1709
1729
|
sasl: {
|
|
1710
1730
|
mechanism: "scram-sha-256",
|
|
@@ -1713,14 +1733,195 @@ var KafkaManager = class extends EventManager {
|
|
|
1713
1733
|
},
|
|
1714
1734
|
logLevel: logLevel.ERROR
|
|
1715
1735
|
});
|
|
1716
|
-
this.consumer = this.kafka.consumer({ groupId:
|
|
1736
|
+
this.consumer = this.kafka.consumer({ groupId: this.groupId });
|
|
1717
1737
|
this.producer = this.kafka.producer();
|
|
1738
|
+
this.brokers = brokers;
|
|
1739
|
+
this.producerConnected = false;
|
|
1740
|
+
this.registerConsumerEventHandlers();
|
|
1718
1741
|
}
|
|
1719
|
-
async
|
|
1720
|
-
|
|
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)
|
|
1721
1789
|
return;
|
|
1722
|
-
|
|
1723
|
-
this.
|
|
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
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
resolveBrokerList(brokers) {
|
|
1824
|
+
const envBrokers = (this.runtimeProcess?.env?.KAFKA_BROKERS ?? "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
1825
|
+
const normalized = [...brokers, ...envBrokers].map((value) => this.normalizeBroker(value)).filter((value) => Boolean(value));
|
|
1826
|
+
const uniqueBrokers = Array.from(new Set(normalized));
|
|
1827
|
+
if (!uniqueBrokers.length) {
|
|
1828
|
+
throw new InternalError(ErrorManager.APP_ERRORS.PROCESS, "Kafka brokers configuration is empty.");
|
|
1829
|
+
}
|
|
1830
|
+
console.info("[KafkaManager] Using brokers", uniqueBrokers);
|
|
1831
|
+
return uniqueBrokers;
|
|
1832
|
+
}
|
|
1833
|
+
normalizeBroker(broker) {
|
|
1834
|
+
if (!broker)
|
|
1835
|
+
return null;
|
|
1836
|
+
const trimmed = broker.trim();
|
|
1837
|
+
if (!trimmed)
|
|
1838
|
+
return null;
|
|
1839
|
+
if (trimmed.startsWith("PLAINTEXT://") || trimmed.startsWith("SSL://")) {
|
|
1840
|
+
return trimmed;
|
|
1841
|
+
}
|
|
1842
|
+
return trimmed.includes(":") ? trimmed : `${trimmed}:9092`;
|
|
1843
|
+
}
|
|
1844
|
+
registerConsumerEventHandlers() {
|
|
1845
|
+
const events = this.consumer.events;
|
|
1846
|
+
this.consumer.on(events.REBALANCING, async (event) => {
|
|
1847
|
+
await this.logConsumerEvent("REBALANCING", event);
|
|
1848
|
+
});
|
|
1849
|
+
const rebalancedEvent = this.consumer.events.REBALANCED;
|
|
1850
|
+
if (rebalancedEvent) {
|
|
1851
|
+
this.consumer.on(rebalancedEvent, async (event) => {
|
|
1852
|
+
await this.logConsumerEvent("REBALANCED", event);
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
this.consumer.on(events.REQUEST_TIMEOUT, async (event) => {
|
|
1856
|
+
await this.logConsumerEvent("REQUEST_TIMEOUT", event, "error");
|
|
1857
|
+
this.handleKafkaError(event?.payload?.error, "consumer.REQUEST_TIMEOUT", { broker: event?.payload?.broker });
|
|
1858
|
+
});
|
|
1859
|
+
this.consumer.on(events.CRASH, async (event) => {
|
|
1860
|
+
await this.logConsumerEvent("CRASH", event, "error");
|
|
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
|
+
}
|
|
1865
|
+
});
|
|
1866
|
+
this.consumer.on(events.DISCONNECT, async (event) => {
|
|
1867
|
+
await this.logConsumerEvent("DISCONNECT", event, "error");
|
|
1868
|
+
});
|
|
1869
|
+
this.consumer.on(events.CONNECT, async (event) => {
|
|
1870
|
+
await this.logConsumerEvent("CONNECT", event);
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
async logConsumerEvent(eventName, kafkaEvent, level = "info") {
|
|
1874
|
+
const payload = kafkaEvent?.payload ?? {};
|
|
1875
|
+
const broker = payload?.broker ?? payload?.coordinator ?? payload?.leaderId ?? null;
|
|
1876
|
+
const error = payload?.error ? {
|
|
1877
|
+
type: payload.error.type ?? payload.error.name,
|
|
1878
|
+
message: payload.error.message
|
|
1879
|
+
} : void 0;
|
|
1880
|
+
const context = {
|
|
1881
|
+
event: eventName,
|
|
1882
|
+
timestamp: kafkaEvent?.timestamp ?? DateTime.now().value,
|
|
1883
|
+
broker,
|
|
1884
|
+
brokers: this.brokers,
|
|
1885
|
+
groupId: payload?.groupId,
|
|
1886
|
+
memberId: payload?.memberId,
|
|
1887
|
+
leaderId: payload?.leaderId,
|
|
1888
|
+
clientId: payload?.clientId,
|
|
1889
|
+
topicPartitions: payload?.topicPartitions,
|
|
1890
|
+
error
|
|
1891
|
+
};
|
|
1892
|
+
const message = `[KafkaManager][${eventName}] ${error?.message ?? ""}`.trim();
|
|
1893
|
+
if (level === "error") {
|
|
1894
|
+
console.error(message, context);
|
|
1895
|
+
await this.execCallback(this._onError, { message, context });
|
|
1896
|
+
} else {
|
|
1897
|
+
console.info(message, context);
|
|
1898
|
+
await this.execCallback(this._onReconnect, { message, context });
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
handleKafkaError(error, origin, metadata = {}) {
|
|
1902
|
+
if (!error)
|
|
1903
|
+
return;
|
|
1904
|
+
const errorType = error.type ?? error.name;
|
|
1905
|
+
const isNotLeaderError = errorType === "NOT_LEADER_FOR_PARTITION" || error?.name === "KafkaJSNotLeaderForPartition";
|
|
1906
|
+
if (isNotLeaderError) {
|
|
1907
|
+
const payload = {
|
|
1908
|
+
origin,
|
|
1909
|
+
broker: error?.broker ?? metadata?.broker,
|
|
1910
|
+
topic: error?.topic,
|
|
1911
|
+
partition: error?.partition,
|
|
1912
|
+
metadata
|
|
1913
|
+
};
|
|
1914
|
+
console.error("[KafkaManager][NOT_LEADER_FOR_PARTITION] Reconnecting to new leader", payload);
|
|
1915
|
+
void this.execCallback(this._onCrash, {
|
|
1916
|
+
message: "[KafkaManager] NOT_LEADER_FOR_PARTITION detected. Reconnecting to new leader.",
|
|
1917
|
+
context: payload
|
|
1918
|
+
});
|
|
1919
|
+
void this.recoverFromLeaderChange(origin);
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
if (error instanceof KafkaJSRequestTimeoutError || errorType === "REQUEST_TIMEOUT") {
|
|
1923
|
+
console.warn("[KafkaManager][REQUEST_TIMEOUT]", { origin, metadata });
|
|
1924
|
+
}
|
|
1724
1925
|
}
|
|
1725
1926
|
async commitMessage(topic, partition, message) {
|
|
1726
1927
|
const next = (BigInt(message.offset) + 1n).toString();
|
|
@@ -1728,9 +1929,10 @@ var KafkaManager = class extends EventManager {
|
|
|
1728
1929
|
}
|
|
1729
1930
|
async run(autocommit = false) {
|
|
1730
1931
|
const __run = async () => {
|
|
1932
|
+
await this.waitForLeaderSelection();
|
|
1731
1933
|
await this.execCallback(this._onStart, { message: `--- ${this.constructor.name} started ---` });
|
|
1732
1934
|
await this.consumer.connect();
|
|
1733
|
-
await this.execCallback(this._onConnected, { message: `--- ${this.constructor.name} connected to ${this.
|
|
1935
|
+
await this.execCallback(this._onConnected, { message: `--- ${this.constructor.name} connected to ${this.brokers.join(", ")} ---` });
|
|
1734
1936
|
for (let topic of this._topics) {
|
|
1735
1937
|
try {
|
|
1736
1938
|
await this.consumer.subscribe({ topic, fromBeginning: false });
|
|
@@ -1767,6 +1969,7 @@ var KafkaManager = class extends EventManager {
|
|
|
1767
1969
|
await heartbeat();
|
|
1768
1970
|
} catch (error) {
|
|
1769
1971
|
await this.execCallback(this._onError, error);
|
|
1972
|
+
this.handleKafkaError(error, "consumer.eachMessage", { topic, partition });
|
|
1770
1973
|
throw error;
|
|
1771
1974
|
}
|
|
1772
1975
|
}
|
|
@@ -1779,6 +1982,7 @@ var KafkaManager = class extends EventManager {
|
|
|
1779
1982
|
}
|
|
1780
1983
|
}
|
|
1781
1984
|
async send(e) {
|
|
1985
|
+
await this.waitForLeaderSelection();
|
|
1782
1986
|
const evt = {
|
|
1783
1987
|
date: DateTime.now().value,
|
|
1784
1988
|
tenant: e.tenant.value,
|
|
@@ -1807,9 +2011,6 @@ var KafkaManager = class extends EventManager {
|
|
|
1807
2011
|
}
|
|
1808
2012
|
}
|
|
1809
2013
|
async start(autocommit = false) {
|
|
1810
|
-
this.consumer.on(this.consumer.events.CRASH, async (error) => {
|
|
1811
|
-
await this.execCallback(this._onError, new InternalError(ErrorManager.APP_ERRORS.PROCESS, error.payload.error.stack));
|
|
1812
|
-
});
|
|
1813
2014
|
await this.run();
|
|
1814
2015
|
}
|
|
1815
2016
|
async pause() {
|