From a2d3de12642b6e18eb0a1fc6037880583dd52f08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 00:37:23 +0000 Subject: [PATCH 1/3] Initial plan From c5925d03bbf7cd10d09f51d7e069cc6f7b6a776b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 00:39:17 +0000 Subject: [PATCH 2/3] Initial plan for renaming CLIENT to HOST Co-authored-by: Nevercold <32977558+Nevercold@users.noreply.github.com> --- dist/index.d.mts | 332 ++++++++++ dist/index.d.ts | 332 ++++++++++ dist/index.js | 1576 ++++++++++++++++++++++++++++++++++++++++++++ dist/index.js.map | 1 + dist/index.mjs | 1526 ++++++++++++++++++++++++++++++++++++++++++ dist/index.mjs.map | 1 + 6 files changed, 3768 insertions(+) create mode 100644 dist/index.d.mts create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/index.js.map create mode 100644 dist/index.mjs create mode 100644 dist/index.mjs.map diff --git a/dist/index.d.mts b/dist/index.d.mts new file mode 100644 index 0000000..c781ae2 --- /dev/null +++ b/dist/index.d.mts @@ -0,0 +1,332 @@ +import { Connection, Server } from 'net-ipc'; +import { GatewayIntentsString, Snowflake, Client } from 'discord.js'; +import { ChildProcess } from 'child_process'; + +type EventPayload = { + id: string; + type: 'message' | 'request' | 'response' | 'response_error'; + data: unknown; +}; + +declare class EventManager { + private pendingPayloads; + private pendingTimeouts; + private readonly _send; + private readonly _on; + private readonly _request; + constructor(send: (payload: EventPayload) => Promise, on: (message: unknown) => void, request: (message: unknown) => unknown); + send(data: unknown): Promise; + request(payload: unknown, timeout: number): Promise; + receive(possiblePayload: unknown): void; + close(reason?: string): void; +} + +declare enum BridgeClientConnectionStatus { + READY = "ready", + PENDING_STOP = "pending_stop" +} +declare class BridgeClientConnection { + readonly instanceID: number; + readonly eventManager: EventManager; + readonly connection: Connection; + readonly data: unknown; + connectionStatus: BridgeClientConnectionStatus; + readonly dev: boolean; + readonly establishedAt: number; + private _onMessage?; + private _onRequest?; + constructor(instanceID: number, connection: Connection, data: unknown, dev: boolean); + messageReceive(message: any): void; + onRequest(callback: (message: unknown) => unknown): void; + onMessage(callback: (message: unknown) => void): void; +} + +declare enum BridgeClientClusterConnectionStatus { + REQUESTING = "requesting", + STARTING = "starting", + CONNECTED = "connected", + RECLUSTERING = "reclustering", + DISCONNECTED = "disconnected" +} +declare class BridgeClientCluster { + readonly clusterID: number; + readonly shardList: number[]; + connectionStatus: BridgeClientClusterConnectionStatus; + connection?: BridgeClientConnection; + oldConnection?: BridgeClientConnection; + missedHeartbeats: number; + heartbeatResponse?: HeartbeatResponse; + heartbeatPending: boolean; + startedAt?: number; + constructor(clusterID: number, shardList: number[]); + setConnection(connection?: BridgeClientConnection): void; + setOldConnection(connection?: BridgeClientConnection): void; + isUsed(): boolean; + reclustering(connection: BridgeClientConnection): void; + addMissedHeartbeat(): void; + removeMissedHeartbeat(): void; + resetMissedHeartbeats(): void; +} +type HeartbeatResponse = { + cpu: { + raw: { + user: number; + system: number; + }; + cpuPercent: string; + }; + memory: { + raw: { + rss: number; + heapTotal: number; + heapUsed: number; + external: number; + arrayBuffers: number; + }; + memoryPercent: string; + usage: number; + }; + ping: number; + shardPings: { + id: number; + ping: number; + status: number; + guilds: number; + members: number; + }[]; +}; + +declare class Bridge { + readonly port: number; + readonly server: Server; + readonly connectedClients: Map; + private readonly token; + private readonly intents; + private readonly shardsPerCluster; + private readonly clusterToStart; + private readonly reclusteringTimeoutInMs; + private readonly clusterCalculator; + private readonly eventMap; + constructor(port: number, token: string, intents: GatewayIntentsString[], shardsPerCluster: number, clusterToStart: number, reclusteringTimeoutInMs: number); + start(): void; + private interval; + private checkRecluster; + private heartbeat; + private checkCreate; + private createCluster; + startListening(): void; + sendMessageToClient(clientId: string, message: unknown): void; + private getTotalShards; + on(event: K, listener: BridgeEventListeners[K]): void; + getClusters(): BridgeClientCluster[]; + stopAllInstances(): Promise; + stopAllInstancesWithRestart(): Promise; + moveCluster(instance: BridgeClientConnection, cluster: BridgeClientCluster): Promise; + stopInstance(instance: BridgeClientConnection, recluster?: boolean): Promise; + sendRequestToGuild(cluster: BridgeClientCluster, guildID: Snowflake, data: unknown, timeout?: number): Promise; +} +type BridgeEventListeners = { + 'CLUSTER_READY': ((cluster: BridgeClientCluster, guilds: number, members: number) => void) | undefined; + 'CLUSTER_STOPPED': ((cluster: BridgeClientCluster) => void) | undefined; + 'CLUSTER_SPAWNED': ((cluster: BridgeClientCluster, connection: BridgeClientConnection) => void) | undefined; + 'CLUSTER_RECLUSTER': ((cluster: BridgeClientCluster, newConnection: BridgeClientConnection, oldConnection: BridgeClientConnection) => void) | undefined; + 'CLUSTER_HEARTBEAT_FAILED': ((cluster: BridgeClientCluster, error: unknown) => void) | undefined; + 'CLIENT_CONNECTED': ((client: BridgeClientConnection) => void) | undefined; + 'CLIENT_DISCONNECTED': ((client: BridgeClientConnection, reason: string) => void) | undefined; + 'ERROR': ((error: string) => void) | undefined; + 'CLIENT_STOP': ((instance: BridgeClientConnection) => void) | undefined; +}; + +/** + * Manages the calculation and distribution of clusters for a Discord bot sharding system. + * This class is responsible for creating clusters with their assigned shards, + * tracking which clusters are in use, and providing methods to retrieve available clusters. + */ +declare class ClusterCalculator { + /** The total number of clusters to initialize */ + private readonly clusterToStart; + /** The number of shards that each cluster will manage */ + private readonly shardsPerCluster; + /** List of all clusters managed by this calculator */ + readonly clusterList: BridgeClientCluster[]; + /** + * Creates a new ClusterCalculator and initializes the clusters. + * + * @param clusterToStart - The number of clusters to create + * @param shardsPerCluster - The number of shards each cluster will manage + */ + constructor(clusterToStart: number, shardsPerCluster: number); + /** + * Calculates and initializes all clusters with their assigned shards. + * Each cluster is assigned a sequential range of shard IDs based on its cluster index. + */ + private calculateClusters; + /** + * Retrieves the next available (unused) cluster and marks it as used. + * + * @returns The next available cluster, or undefined if all clusters are in use + */ + getNextCluster(): BridgeClientCluster | undefined; + /** + * Retrieves multiple available clusters up to the specified count. + * Each returned cluster is marked as used. + * + * @param count - The maximum number of clusters to retrieve + * @returns An array of available clusters (may be fewer than requested if not enough are available) + */ + getNextClusters(count: number): BridgeClientCluster[]; + /** + * Sets the used status of a specific cluster by its ID. + * + * @param clusterID - The ID of the cluster to update + * @param connection - The connection to associate with the cluster + */ + clearClusterConnection(clusterID: number): void; + getClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[]; + getOldClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[]; + checkAllClustersConnected(): boolean; + findMostAndLeastClustersForConnections(connectedClients: BridgeClientConnection[]): { + most: BridgeClientConnection | undefined; + least: BridgeClientConnection | undefined; + }; + getClusterWithLowestLoad(connectedClients: Map): BridgeClientConnection | undefined; + getClusterOfShard(shardID: number): BridgeClientCluster | undefined; +} + +declare class Cluster { + readonly instanceID: number; + readonly clusterID: number; + readonly shardList: number[]; + readonly totalShards: number; + readonly token: string; + readonly intents: GatewayIntentsString[]; + eventManager: EventManager; + client: T; + onSelfDestruct?: () => void; + private readonly eventMap; + constructor(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]); + static initial(): Cluster; + triggerReady(guilds: number, members: number): void; + triggerError(e: any): void; + private wait; + private _onMessage; + private _onRequest; + on(event: K, listener: ClusterEventListeners[K]): void; + sendMessage(data: unknown): void; + sendRequest(data: unknown, timeout?: number): Promise; + broadcastEval(fn: (cluster: T) => Result, timeout?: number): Promise; + sendMessageToClusterOfGuild(guildID: string, message: unknown): void; + sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout?: number): Promise; +} +type ClusterEventListeners = { + message: (message: unknown) => void; + request: (message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void; + CLUSTER_READY: () => void; +}; + +type ClusterProcessState = 'starting' | 'running' | 'stopped'; +declare class ClusterProcess { + readonly child: ChildProcess; + readonly eventManager: EventManager; + readonly id: number; + readonly shardList: number[]; + readonly totalShards: number; + status: ClusterProcessState; + readonly createdAt: number; + private _onMessage?; + private _onRequest?; + constructor(id: number, child: ChildProcess, shardList: number[], totalShards: number); + onMessage(callback: (message: unknown) => void): void; + onRequest(callback: (message: unknown) => unknown): void; + sendMessage(data: unknown): void; + sendRequest(data: unknown, timeout?: number): Promise; +} + +declare class ShardingUtil { + static getShardIDForGuild(guildID: string, totalShards: number): number; +} + +declare abstract class BotInstance { + private readonly entryPoint; + private readonly execArgv; + readonly clients: Map; + protected constructor(entryPoint: string, execArgv?: string[]); + protected readonly eventMap: BotInstanceEventListeners; + protected startProcess(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]): void; + protected killProcess(client: ClusterProcess, reason: string): void; + protected abstract setClusterStopped(client: ClusterProcess, reason: string): void; + protected abstract setClusterReady(client: ClusterProcess, guilds: number, members: number): void; + protected abstract setClusterSpawned(client: ClusterProcess): void; + abstract start(): void; + private onMessage; + protected abstract onRequest(client: ClusterProcess, message: any): Promise; + on(event: K, listener: BotInstanceEventListeners[K]): void; + sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout?: number): Promise; + sendRequestToCluster(cluster: ClusterProcess, message: unknown, timeout?: number): Promise; +} +type BotInstanceEventListeners = { + 'message': ((client: ClusterProcess, message: unknown) => void) | undefined; + 'request': ((client: ClusterProcess, message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined; + 'PROCESS_KILLED': ((client: ClusterProcess, reason: string, processKilled: boolean) => void) | undefined; + 'PROCESS_SELF_DESTRUCT_ERROR': ((client: ClusterProcess, reason: string, error: unknown) => void) | undefined; + 'PROCESS_SPAWNED': ((client: ClusterProcess) => void) | undefined; + 'PROCESS_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined; + 'CLUSTER_READY': ((client: ClusterProcess) => void) | undefined; + 'CLUSTER_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined; + 'CLUSTER_RECLUSTER': ((client: ClusterProcess) => void) | undefined; + 'ERROR': ((error: string) => void) | undefined; + 'BRIDGE_CONNECTION_ESTABLISHED': (() => void) | undefined; + 'BRIDGE_CONNECTION_CLOSED': ((reason: string) => void) | undefined; + 'BRIDGE_CONNECTION_STATUS_CHANGE': ((status: number) => void) | undefined; + 'INSTANCE_STOP': (() => void) | undefined; + 'INSTANCE_STOPPED': (() => void) | undefined; + 'SELF_CHECK_SUCCESS': (() => void) | undefined; + 'SELF_CHECK_ERROR': ((error: string) => void) | undefined; + 'SELF_CHECK_RECEIVED': ((data: { + clusterList: number[]; + }) => void) | undefined; +}; + +declare enum BridgeConnectionStatus { + CONNECTED = 0, + DISCONNECTED = 1 +} +declare class ManagedInstance extends BotInstance { + private readonly host; + private readonly port; + private readonly instanceID; + private eventManager; + private connectionStatus; + private data; + private dev; + constructor(entryPoint: string, host: string, port: number, instanceID: number, data: unknown, execArgv?: string[], dev?: boolean); + start(): void; + private selfCheck; + protected setClusterStopped(client: ClusterProcess, reason: string): void; + protected setClusterReady(client: ClusterProcess, guilds: number, members: number): void; + protected setClusterSpawned(client: ClusterProcess): void; + private onClusterCreate; + private onClusterStop; + private onClusterRecluster; + protected onRequest(client: ClusterProcess, message: any): Promise; + private onBridgeRequest; + stopInstance(): void; +} + +declare class StandaloneInstance extends BotInstance { + private readonly totalClusters; + private readonly shardsPerCluster; + readonly token: string; + readonly intents: GatewayIntentsString[]; + constructor(entryPoint: string, shardsPerCluster: number, totalClusters: number, token: string, intents: GatewayIntentsString[], execArgv?: string[]); + get totalShards(): number; + private calculateClusters; + start(): void; + protected setClusterStopped(client: ClusterProcess, reason: string): void; + protected setClusterReady(client: ClusterProcess): void; + protected setClusterSpawned(client: ClusterProcess): void; + private restartProcess; + protected onRequest(client: ClusterProcess, message: any): Promise; +} + +export { BotInstance, type BotInstanceEventListeners, Bridge, BridgeClientCluster, BridgeClientClusterConnectionStatus, BridgeClientConnection, BridgeClientConnectionStatus, BridgeConnectionStatus, type BridgeEventListeners, Cluster, ClusterCalculator, type ClusterEventListeners, ClusterProcess, type ClusterProcessState, EventManager, type EventPayload, type HeartbeatResponse, ManagedInstance, ShardingUtil, StandaloneInstance }; diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..c781ae2 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,332 @@ +import { Connection, Server } from 'net-ipc'; +import { GatewayIntentsString, Snowflake, Client } from 'discord.js'; +import { ChildProcess } from 'child_process'; + +type EventPayload = { + id: string; + type: 'message' | 'request' | 'response' | 'response_error'; + data: unknown; +}; + +declare class EventManager { + private pendingPayloads; + private pendingTimeouts; + private readonly _send; + private readonly _on; + private readonly _request; + constructor(send: (payload: EventPayload) => Promise, on: (message: unknown) => void, request: (message: unknown) => unknown); + send(data: unknown): Promise; + request(payload: unknown, timeout: number): Promise; + receive(possiblePayload: unknown): void; + close(reason?: string): void; +} + +declare enum BridgeClientConnectionStatus { + READY = "ready", + PENDING_STOP = "pending_stop" +} +declare class BridgeClientConnection { + readonly instanceID: number; + readonly eventManager: EventManager; + readonly connection: Connection; + readonly data: unknown; + connectionStatus: BridgeClientConnectionStatus; + readonly dev: boolean; + readonly establishedAt: number; + private _onMessage?; + private _onRequest?; + constructor(instanceID: number, connection: Connection, data: unknown, dev: boolean); + messageReceive(message: any): void; + onRequest(callback: (message: unknown) => unknown): void; + onMessage(callback: (message: unknown) => void): void; +} + +declare enum BridgeClientClusterConnectionStatus { + REQUESTING = "requesting", + STARTING = "starting", + CONNECTED = "connected", + RECLUSTERING = "reclustering", + DISCONNECTED = "disconnected" +} +declare class BridgeClientCluster { + readonly clusterID: number; + readonly shardList: number[]; + connectionStatus: BridgeClientClusterConnectionStatus; + connection?: BridgeClientConnection; + oldConnection?: BridgeClientConnection; + missedHeartbeats: number; + heartbeatResponse?: HeartbeatResponse; + heartbeatPending: boolean; + startedAt?: number; + constructor(clusterID: number, shardList: number[]); + setConnection(connection?: BridgeClientConnection): void; + setOldConnection(connection?: BridgeClientConnection): void; + isUsed(): boolean; + reclustering(connection: BridgeClientConnection): void; + addMissedHeartbeat(): void; + removeMissedHeartbeat(): void; + resetMissedHeartbeats(): void; +} +type HeartbeatResponse = { + cpu: { + raw: { + user: number; + system: number; + }; + cpuPercent: string; + }; + memory: { + raw: { + rss: number; + heapTotal: number; + heapUsed: number; + external: number; + arrayBuffers: number; + }; + memoryPercent: string; + usage: number; + }; + ping: number; + shardPings: { + id: number; + ping: number; + status: number; + guilds: number; + members: number; + }[]; +}; + +declare class Bridge { + readonly port: number; + readonly server: Server; + readonly connectedClients: Map; + private readonly token; + private readonly intents; + private readonly shardsPerCluster; + private readonly clusterToStart; + private readonly reclusteringTimeoutInMs; + private readonly clusterCalculator; + private readonly eventMap; + constructor(port: number, token: string, intents: GatewayIntentsString[], shardsPerCluster: number, clusterToStart: number, reclusteringTimeoutInMs: number); + start(): void; + private interval; + private checkRecluster; + private heartbeat; + private checkCreate; + private createCluster; + startListening(): void; + sendMessageToClient(clientId: string, message: unknown): void; + private getTotalShards; + on(event: K, listener: BridgeEventListeners[K]): void; + getClusters(): BridgeClientCluster[]; + stopAllInstances(): Promise; + stopAllInstancesWithRestart(): Promise; + moveCluster(instance: BridgeClientConnection, cluster: BridgeClientCluster): Promise; + stopInstance(instance: BridgeClientConnection, recluster?: boolean): Promise; + sendRequestToGuild(cluster: BridgeClientCluster, guildID: Snowflake, data: unknown, timeout?: number): Promise; +} +type BridgeEventListeners = { + 'CLUSTER_READY': ((cluster: BridgeClientCluster, guilds: number, members: number) => void) | undefined; + 'CLUSTER_STOPPED': ((cluster: BridgeClientCluster) => void) | undefined; + 'CLUSTER_SPAWNED': ((cluster: BridgeClientCluster, connection: BridgeClientConnection) => void) | undefined; + 'CLUSTER_RECLUSTER': ((cluster: BridgeClientCluster, newConnection: BridgeClientConnection, oldConnection: BridgeClientConnection) => void) | undefined; + 'CLUSTER_HEARTBEAT_FAILED': ((cluster: BridgeClientCluster, error: unknown) => void) | undefined; + 'CLIENT_CONNECTED': ((client: BridgeClientConnection) => void) | undefined; + 'CLIENT_DISCONNECTED': ((client: BridgeClientConnection, reason: string) => void) | undefined; + 'ERROR': ((error: string) => void) | undefined; + 'CLIENT_STOP': ((instance: BridgeClientConnection) => void) | undefined; +}; + +/** + * Manages the calculation and distribution of clusters for a Discord bot sharding system. + * This class is responsible for creating clusters with their assigned shards, + * tracking which clusters are in use, and providing methods to retrieve available clusters. + */ +declare class ClusterCalculator { + /** The total number of clusters to initialize */ + private readonly clusterToStart; + /** The number of shards that each cluster will manage */ + private readonly shardsPerCluster; + /** List of all clusters managed by this calculator */ + readonly clusterList: BridgeClientCluster[]; + /** + * Creates a new ClusterCalculator and initializes the clusters. + * + * @param clusterToStart - The number of clusters to create + * @param shardsPerCluster - The number of shards each cluster will manage + */ + constructor(clusterToStart: number, shardsPerCluster: number); + /** + * Calculates and initializes all clusters with their assigned shards. + * Each cluster is assigned a sequential range of shard IDs based on its cluster index. + */ + private calculateClusters; + /** + * Retrieves the next available (unused) cluster and marks it as used. + * + * @returns The next available cluster, or undefined if all clusters are in use + */ + getNextCluster(): BridgeClientCluster | undefined; + /** + * Retrieves multiple available clusters up to the specified count. + * Each returned cluster is marked as used. + * + * @param count - The maximum number of clusters to retrieve + * @returns An array of available clusters (may be fewer than requested if not enough are available) + */ + getNextClusters(count: number): BridgeClientCluster[]; + /** + * Sets the used status of a specific cluster by its ID. + * + * @param clusterID - The ID of the cluster to update + * @param connection - The connection to associate with the cluster + */ + clearClusterConnection(clusterID: number): void; + getClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[]; + getOldClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[]; + checkAllClustersConnected(): boolean; + findMostAndLeastClustersForConnections(connectedClients: BridgeClientConnection[]): { + most: BridgeClientConnection | undefined; + least: BridgeClientConnection | undefined; + }; + getClusterWithLowestLoad(connectedClients: Map): BridgeClientConnection | undefined; + getClusterOfShard(shardID: number): BridgeClientCluster | undefined; +} + +declare class Cluster { + readonly instanceID: number; + readonly clusterID: number; + readonly shardList: number[]; + readonly totalShards: number; + readonly token: string; + readonly intents: GatewayIntentsString[]; + eventManager: EventManager; + client: T; + onSelfDestruct?: () => void; + private readonly eventMap; + constructor(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]); + static initial(): Cluster; + triggerReady(guilds: number, members: number): void; + triggerError(e: any): void; + private wait; + private _onMessage; + private _onRequest; + on(event: K, listener: ClusterEventListeners[K]): void; + sendMessage(data: unknown): void; + sendRequest(data: unknown, timeout?: number): Promise; + broadcastEval(fn: (cluster: T) => Result, timeout?: number): Promise; + sendMessageToClusterOfGuild(guildID: string, message: unknown): void; + sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout?: number): Promise; +} +type ClusterEventListeners = { + message: (message: unknown) => void; + request: (message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void; + CLUSTER_READY: () => void; +}; + +type ClusterProcessState = 'starting' | 'running' | 'stopped'; +declare class ClusterProcess { + readonly child: ChildProcess; + readonly eventManager: EventManager; + readonly id: number; + readonly shardList: number[]; + readonly totalShards: number; + status: ClusterProcessState; + readonly createdAt: number; + private _onMessage?; + private _onRequest?; + constructor(id: number, child: ChildProcess, shardList: number[], totalShards: number); + onMessage(callback: (message: unknown) => void): void; + onRequest(callback: (message: unknown) => unknown): void; + sendMessage(data: unknown): void; + sendRequest(data: unknown, timeout?: number): Promise; +} + +declare class ShardingUtil { + static getShardIDForGuild(guildID: string, totalShards: number): number; +} + +declare abstract class BotInstance { + private readonly entryPoint; + private readonly execArgv; + readonly clients: Map; + protected constructor(entryPoint: string, execArgv?: string[]); + protected readonly eventMap: BotInstanceEventListeners; + protected startProcess(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]): void; + protected killProcess(client: ClusterProcess, reason: string): void; + protected abstract setClusterStopped(client: ClusterProcess, reason: string): void; + protected abstract setClusterReady(client: ClusterProcess, guilds: number, members: number): void; + protected abstract setClusterSpawned(client: ClusterProcess): void; + abstract start(): void; + private onMessage; + protected abstract onRequest(client: ClusterProcess, message: any): Promise; + on(event: K, listener: BotInstanceEventListeners[K]): void; + sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout?: number): Promise; + sendRequestToCluster(cluster: ClusterProcess, message: unknown, timeout?: number): Promise; +} +type BotInstanceEventListeners = { + 'message': ((client: ClusterProcess, message: unknown) => void) | undefined; + 'request': ((client: ClusterProcess, message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined; + 'PROCESS_KILLED': ((client: ClusterProcess, reason: string, processKilled: boolean) => void) | undefined; + 'PROCESS_SELF_DESTRUCT_ERROR': ((client: ClusterProcess, reason: string, error: unknown) => void) | undefined; + 'PROCESS_SPAWNED': ((client: ClusterProcess) => void) | undefined; + 'PROCESS_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined; + 'CLUSTER_READY': ((client: ClusterProcess) => void) | undefined; + 'CLUSTER_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined; + 'CLUSTER_RECLUSTER': ((client: ClusterProcess) => void) | undefined; + 'ERROR': ((error: string) => void) | undefined; + 'BRIDGE_CONNECTION_ESTABLISHED': (() => void) | undefined; + 'BRIDGE_CONNECTION_CLOSED': ((reason: string) => void) | undefined; + 'BRIDGE_CONNECTION_STATUS_CHANGE': ((status: number) => void) | undefined; + 'INSTANCE_STOP': (() => void) | undefined; + 'INSTANCE_STOPPED': (() => void) | undefined; + 'SELF_CHECK_SUCCESS': (() => void) | undefined; + 'SELF_CHECK_ERROR': ((error: string) => void) | undefined; + 'SELF_CHECK_RECEIVED': ((data: { + clusterList: number[]; + }) => void) | undefined; +}; + +declare enum BridgeConnectionStatus { + CONNECTED = 0, + DISCONNECTED = 1 +} +declare class ManagedInstance extends BotInstance { + private readonly host; + private readonly port; + private readonly instanceID; + private eventManager; + private connectionStatus; + private data; + private dev; + constructor(entryPoint: string, host: string, port: number, instanceID: number, data: unknown, execArgv?: string[], dev?: boolean); + start(): void; + private selfCheck; + protected setClusterStopped(client: ClusterProcess, reason: string): void; + protected setClusterReady(client: ClusterProcess, guilds: number, members: number): void; + protected setClusterSpawned(client: ClusterProcess): void; + private onClusterCreate; + private onClusterStop; + private onClusterRecluster; + protected onRequest(client: ClusterProcess, message: any): Promise; + private onBridgeRequest; + stopInstance(): void; +} + +declare class StandaloneInstance extends BotInstance { + private readonly totalClusters; + private readonly shardsPerCluster; + readonly token: string; + readonly intents: GatewayIntentsString[]; + constructor(entryPoint: string, shardsPerCluster: number, totalClusters: number, token: string, intents: GatewayIntentsString[], execArgv?: string[]); + get totalShards(): number; + private calculateClusters; + start(): void; + protected setClusterStopped(client: ClusterProcess, reason: string): void; + protected setClusterReady(client: ClusterProcess): void; + protected setClusterSpawned(client: ClusterProcess): void; + private restartProcess; + protected onRequest(client: ClusterProcess, message: any): Promise; +} + +export { BotInstance, type BotInstanceEventListeners, Bridge, BridgeClientCluster, BridgeClientClusterConnectionStatus, BridgeClientConnection, BridgeClientConnectionStatus, BridgeConnectionStatus, type BridgeEventListeners, Cluster, ClusterCalculator, type ClusterEventListeners, ClusterProcess, type ClusterProcessState, EventManager, type EventPayload, type HeartbeatResponse, ManagedInstance, ShardingUtil, StandaloneInstance }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..7617ac6 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,1576 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/index.ts +var index_exports = {}; +__export(index_exports, { + BotInstance: () => BotInstance, + Bridge: () => Bridge, + BridgeClientCluster: () => BridgeClientCluster, + BridgeClientClusterConnectionStatus: () => BridgeClientClusterConnectionStatus, + BridgeClientConnection: () => BridgeClientConnection, + BridgeClientConnectionStatus: () => BridgeClientConnectionStatus, + BridgeConnectionStatus: () => BridgeConnectionStatus, + Cluster: () => Cluster, + ClusterCalculator: () => ClusterCalculator, + ClusterProcess: () => ClusterProcess, + EventManager: () => EventManager, + ManagedInstance: () => ManagedInstance, + ShardingUtil: () => ShardingUtil, + StandaloneInstance: () => StandaloneInstance +}); +module.exports = __toCommonJS(index_exports); + +// src/bridge/BridgeClientCluster.ts +var BridgeClientClusterConnectionStatus = /* @__PURE__ */ ((BridgeClientClusterConnectionStatus2) => { + BridgeClientClusterConnectionStatus2["REQUESTING"] = "requesting"; + BridgeClientClusterConnectionStatus2["STARTING"] = "starting"; + BridgeClientClusterConnectionStatus2["CONNECTED"] = "connected"; + BridgeClientClusterConnectionStatus2["RECLUSTERING"] = "reclustering"; + BridgeClientClusterConnectionStatus2["DISCONNECTED"] = "disconnected"; + return BridgeClientClusterConnectionStatus2; +})(BridgeClientClusterConnectionStatus || {}); +var BridgeClientCluster = class { + clusterID; + shardList; + connectionStatus = "disconnected" /* DISCONNECTED */; + connection; + oldConnection; + missedHeartbeats = 0; + heartbeatResponse; + heartbeatPending = false; + startedAt; + constructor(clusterID, shardList) { + this.clusterID = clusterID; + this.shardList = shardList; + } + setConnection(connection) { + if (connection == void 0) { + this.connectionStatus = "disconnected" /* DISCONNECTED */; + this.connection = void 0; + return; + } + if (this.connection) { + throw new Error(`Connection already set for cluster ${this.clusterID}`); + } + this.connectionStatus = "requesting" /* REQUESTING */; + this.connection = connection; + } + setOldConnection(connection) { + this.oldConnection = connection; + } + isUsed() { + return this.connection != void 0 && this.connectionStatus !== "disconnected" /* DISCONNECTED */; + } + reclustering(connection) { + this.connectionStatus = "reclustering" /* RECLUSTERING */; + this.oldConnection = this.connection; + this.connection = connection; + } + addMissedHeartbeat() { + this.missedHeartbeats++; + } + removeMissedHeartbeat() { + if (this.missedHeartbeats > 0) { + this.missedHeartbeats--; + } + } + resetMissedHeartbeats() { + this.missedHeartbeats = 0; + } +}; + +// src/general/EventManager.ts +var EventManager = class { + pendingPayloads = /* @__PURE__ */ new Map(); + // Track per-request timeout handles so we can clear them on resolve/reject + pendingTimeouts = /* @__PURE__ */ new Map(); + _send; + _on; + _request; + constructor(send, on, request) { + this._send = send; + this._on = on; + this._request = request; + } + async send(data) { + return this._send({ + id: crypto.randomUUID(), + type: "message", + data + }); + } + async request(payload, timeout) { + const id = crypto.randomUUID(); + return new Promise((resolve, reject) => { + this._send({ + id, + type: "request", + data: payload + }); + this.pendingPayloads.set(id, { + resolve, + reject + }); + const t = setTimeout(() => { + if (this.pendingPayloads.has(id)) { + this.pendingPayloads.delete(id); + this.pendingTimeouts.delete(id); + reject({ + error: `Request with id ${id} timed out` + }); + } + }, timeout); + this.pendingTimeouts.set(id, t); + }); + } + receive(possiblePayload) { + if (typeof possiblePayload !== "object" || possiblePayload === null) { + return; + } + const payload = possiblePayload; + if (!payload.id || !payload.type) { + return; + } + if (payload.type === "message") { + this._on(payload.data); + return; + } + if (payload.type === "response") { + const resolve = this.pendingPayloads.get(payload.id)?.resolve; + if (resolve) { + resolve(payload.data); + this.pendingPayloads.delete(payload.id); + const to = this.pendingTimeouts.get(payload.id); + if (to) clearTimeout(to); + this.pendingTimeouts.delete(payload.id); + } + return; + } + if (payload.type === "response_error") { + const reject = this.pendingPayloads.get(payload.id)?.reject; + if (reject) { + reject(payload.data); + this.pendingPayloads.delete(payload.id); + const to = this.pendingTimeouts.get(payload.id); + if (to) clearTimeout(to); + this.pendingTimeouts.delete(payload.id); + } + return; + } + if (payload.type === "request") { + const data = this._request(payload.data); + if (data instanceof Promise) { + data.then((result2) => { + this._send({ + id: payload.id, + type: "response", + data: result2 + }); + }).catch((error) => { + this._send({ + id: payload.id, + type: "response_error", + data: error + }); + }); + } else { + this._send({ + id: payload.id, + type: "response", + data + }); + } + return; + } + } + // Reject and clear all pending requests to avoid memory leaks when a connection/process closes + close(reason) { + if (this.pendingPayloads.size === 0 && this.pendingTimeouts.size === 0) return; + const err = { error: reason || "EventManager closed" }; + for (const [id, handlers] of this.pendingPayloads.entries()) { + try { + handlers.reject(err); + } catch (_) { + } + this.pendingPayloads.delete(id); + const to = this.pendingTimeouts.get(id); + if (to) clearTimeout(to); + this.pendingTimeouts.delete(id); + } + for (const to of this.pendingTimeouts.values()) { + clearTimeout(to); + } + this.pendingTimeouts.clear(); + } +}; + +// src/bridge/BridgeClientConnection.ts +var BridgeClientConnectionStatus = /* @__PURE__ */ ((BridgeClientConnectionStatus2) => { + BridgeClientConnectionStatus2["READY"] = "ready"; + BridgeClientConnectionStatus2["PENDING_STOP"] = "pending_stop"; + return BridgeClientConnectionStatus2; +})(BridgeClientConnectionStatus || {}); +var BridgeClientConnection = class { + instanceID; + eventManager; + connection; + data; + connectionStatus = "ready" /* READY */; + dev = false; + establishedAt = Date.now(); + _onMessage; + _onRequest; + constructor(instanceID, connection, data, dev) { + this.instanceID = instanceID; + this.connection = connection; + this.data = data; + this.dev = dev || false; + this.eventManager = new EventManager((message2) => { + if (!this.connection?.connection?.closed) { + return this.connection.send(message2); + } + return Promise.reject(new Error("Connection is closed, cannot send message")); + }, (message2) => { + if (this._onMessage) { + this._onMessage(message2); + } + }, (message2) => { + if (this._onRequest) { + return this._onRequest(message2); + } + return void 0; + }); + } + messageReceive(message2) { + this.eventManager.receive(message2); + } + onRequest(callback) { + this._onRequest = callback; + } + onMessage(callback) { + this._onMessage = callback; + } +}; + +// src/bridge/Bridge.ts +var import_net_ipc = require("net-ipc"); + +// src/bridge/ClusterCalculator.ts +var ClusterCalculator = class { + /** The total number of clusters to initialize */ + clusterToStart; + /** The number of shards that each cluster will manage */ + shardsPerCluster; + /** List of all clusters managed by this calculator */ + clusterList = []; + /** + * Creates a new ClusterCalculator and initializes the clusters. + * + * @param clusterToStart - The number of clusters to create + * @param shardsPerCluster - The number of shards each cluster will manage + */ + constructor(clusterToStart, shardsPerCluster) { + this.shardsPerCluster = shardsPerCluster; + this.clusterToStart = clusterToStart; + this.calculateClusters(); + } + /** + * Calculates and initializes all clusters with their assigned shards. + * Each cluster is assigned a sequential range of shard IDs based on its cluster index. + */ + calculateClusters() { + const clusters = /* @__PURE__ */ new Map(); + for (let i = 0; i < this.clusterToStart; i++) { + clusters.set(i, []); + for (let j = 0; j < this.shardsPerCluster; j++) { + clusters.get(i)?.push(i * this.shardsPerCluster + j); + } + } + for (let [clusterIndex, clusterShards] of clusters.entries()) { + this.clusterList.push(new BridgeClientCluster(clusterIndex, clusterShards)); + } + } + /** + * Retrieves the next available (unused) cluster and marks it as used. + * + * @returns The next available cluster, or undefined if all clusters are in use + */ + getNextCluster() { + for (const cluster of this.clusterList) { + if (!cluster.isUsed()) { + return cluster; + } + } + return void 0; + } + /** + * Retrieves multiple available clusters up to the specified count. + * Each returned cluster is marked as used. + * + * @param count - The maximum number of clusters to retrieve + * @returns An array of available clusters (may be fewer than requested if not enough are available) + */ + getNextClusters(count) { + const availableClusters = []; + for (const cluster of this.clusterList) { + if (!cluster.isUsed() && availableClusters.length < count) { + availableClusters.push(cluster); + } + } + return availableClusters; + } + /** + * Sets the used status of a specific cluster by its ID. + * + * @param clusterID - The ID of the cluster to update + * @param connection - The connection to associate with the cluster + */ + clearClusterConnection(clusterID) { + const cluster = this.clusterList.find((c) => c.clusterID === clusterID); + if (cluster) { + cluster.setConnection(void 0); + } + } + getClusterForConnection(connection) { + return this.clusterList.filter( + (cluster) => cluster.connection?.instanceID === connection.instanceID + ); + } + getOldClusterForConnection(connection) { + return this.clusterList.filter( + (cluster) => cluster.oldConnection?.instanceID === connection.instanceID + ); + } + checkAllClustersConnected() { + for (const cluster of this.clusterList) { + if (cluster.connectionStatus != "connected" /* CONNECTED */) { + return false; + } + } + return true; + } + findMostAndLeastClustersForConnections(connectedClients) { + const openClients = connectedClients.filter((x) => !x.dev); + const devClients = connectedClients.filter((x) => x.dev); + const summDevConnectedClusters = devClients.map((c) => this.getClusterForConnection(c).length).reduce((a, b) => a + b, 0); + let most; + let least; + let remainder = (this.clusterToStart - summDevConnectedClusters) % openClients.length || 0; + for (const client of openClients) { + const clusters = this.getClusterForConnection(client); + if (!most || clusters.length > this.getClusterForConnection(most).length) { + most = client; + } + if (!least || clusters.length < this.getClusterForConnection(least).length) { + least = client; + } + } + if (most && least) { + const mostCount = this.getClusterForConnection(most).length; + const leastCount = this.getClusterForConnection(least).length; + if (mostCount - leastCount <= remainder) { + return { most: void 0, least: void 0 }; + } + } + return { most, least }; + } + getClusterWithLowestLoad(connectedClients) { + let lowestLoadClient; + let lowestLoad = Infinity; + for (const client of connectedClients.values().filter((c) => c.connectionStatus === "ready" /* READY */ && !c.dev)) { + const clusters = this.getClusterForConnection(client); + const load = clusters.length; + if (load < lowestLoad) { + lowestLoad = load; + lowestLoadClient = client; + } + } + return lowestLoadClient; + } + getClusterOfShard(shardID) { + return this.clusterList.find((c) => c.shardList.includes(shardID)); + } +}; + +// src/general/ShardingUtil.ts +var ShardingUtil = class { + static getShardIDForGuild(guildID, totalShards) { + if (!guildID || totalShards <= 0) { + throw new Error("Invalid guild ID or total shards"); + } + return Number(BigInt(guildID) >> 22n) % totalShards; + } +}; + +// src/bridge/Bridge.ts +var Bridge = class { + port; + server; + connectedClients = /* @__PURE__ */ new Map(); + token; + intents; + shardsPerCluster = 1; + clusterToStart = 1; + reclusteringTimeoutInMs; + clusterCalculator; + eventMap = { + CLUSTER_READY: void 0, + CLUSTER_HEARTBEAT_FAILED: void 0, + CLUSTER_STOPPED: void 0, + CLIENT_CONNECTED: void 0, + CLIENT_DISCONNECTED: void 0, + CLUSTER_SPAWNED: void 0, + CLUSTER_RECLUSTER: void 0, + ERROR: void 0, + CLIENT_STOP: void 0 + }; + constructor(port, token, intents, shardsPerCluster, clusterToStart, reclusteringTimeoutInMs) { + this.port = port; + this.token = token; + this.intents = intents; + this.clusterToStart = clusterToStart; + this.shardsPerCluster = shardsPerCluster; + this.reclusteringTimeoutInMs = reclusteringTimeoutInMs; + this.clusterCalculator = new ClusterCalculator(this.clusterToStart, this.shardsPerCluster); + this.server = new import_net_ipc.Server({ + port: this.port + }); + } + start() { + this.server.start().then(() => { + this.startListening(); + }); + this.interval(); + } + interval() { + setInterval(() => { + this.checkCreate(); + this.checkRecluster(); + this.heartbeat(); + }, 5e3); + } + checkRecluster() { + const up = this.clusterCalculator.checkAllClustersConnected(); + if (!up) { + return; + } + const connectedClients = this.connectedClients.values().filter((c) => c.connectionStatus == "ready" /* READY */).filter((c) => !c.dev).filter((c) => c.establishedAt + this.reclusteringTimeoutInMs < Date.now()).toArray(); + const { most, least } = this.clusterCalculator.findMostAndLeastClustersForConnections(connectedClients); + if (most) { + const clusterToSteal = this.clusterCalculator.getClusterForConnection(most)[0] || void 0; + if (least && clusterToSteal) { + clusterToSteal.reclustering(least); + if (this.eventMap.CLUSTER_RECLUSTER) this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection); + this.createCluster(least, clusterToSteal, true); + return; + } + } + } + heartbeat() { + const clusters = this.clusterCalculator.clusterList; + clusters.forEach((cluster) => { + if (cluster.connection && cluster.connectionStatus == "connected" /* CONNECTED */ && !cluster.heartbeatPending) { + cluster.heartbeatPending = true; + cluster.connection.eventManager.request({ + type: "CLUSTER_HEARTBEAT", + data: { + clusterID: cluster.clusterID + } + }, 2e4).then((r) => { + cluster.removeMissedHeartbeat(); + cluster.heartbeatResponse = r; + }).catch((err) => { + if (this.eventMap.CLUSTER_HEARTBEAT_FAILED) this.eventMap.CLUSTER_HEARTBEAT_FAILED(cluster, err); + cluster.addMissedHeartbeat(); + if (cluster.missedHeartbeats > 7 && !cluster.connection?.dev) { + cluster.connection?.eventManager.send({ + type: "CLUSTER_STOP", + data: { + id: cluster.clusterID + } + }); + cluster.connectionStatus = "disconnected" /* DISCONNECTED */; + cluster.resetMissedHeartbeats(); + } + }).finally(() => { + cluster.heartbeatPending = false; + }); + } + }); + } + checkCreate() { + const optionalCluster = this.clusterCalculator.getNextCluster(); + if (!optionalCluster) { + return; + } + const lowestLoadClient = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients); + if (!lowestLoadClient) { + return; + } + this.createCluster(lowestLoadClient, optionalCluster); + } + createCluster(connection, cluster, recluster = false) { + cluster.resetMissedHeartbeats(); + cluster.heartbeatResponse = void 0; + if (!recluster) { + cluster.setConnection(connection); + } else { + cluster.oldConnection?.eventManager.send({ + type: "CLUSTER_RECLUSTER", + data: { + clusterID: cluster.clusterID + } + }); + } + if (this.eventMap.CLUSTER_SPAWNED) this.eventMap.CLUSTER_SPAWNED(cluster, connection); + connection.eventManager.send({ + type: "CLUSTER_CREATE", + data: { + clusterID: cluster.clusterID, + instanceID: connection.instanceID, + totalShards: this.getTotalShards(), + shardList: cluster.shardList, + token: this.token, + intents: this.intents + } + }); + } + startListening() { + this.server.on("connect", (connection, payload) => { + const id = payload?.id; + const data = payload.data; + const dev = payload?.dev || false; + if (!id) { + connection.close("Invalid payload", false); + return; + } + if (this.connectedClients.values().some((client) => client.instanceID === id)) { + connection.close("Already connected", false); + return; + } + const bridgeConnection = new BridgeClientConnection(payload.id, connection, data, dev); + if (this.eventMap.CLIENT_CONNECTED) this.eventMap.CLIENT_CONNECTED(bridgeConnection); + bridgeConnection.onMessage((m2) => { + if (m2.type == "CLUSTER_SPAWNED") { + const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); + if (cluster) { + cluster.connectionStatus = "starting" /* STARTING */; + } + return; + } + if (m2.type == "CLUSTER_READY") { + const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); + if (cluster) { + cluster.startedAt = Date.now(); + if (this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(cluster, m2.data.guilds || 0, m2.data.members || 0); + cluster.connectionStatus = "connected" /* CONNECTED */; + if (cluster.oldConnection) { + cluster.oldConnection.eventManager.send({ + type: "CLUSTER_STOP", + data: { + id: cluster.clusterID + } + }); + cluster.oldConnection = void 0; + } + } + return; + } + if (m2.type == "CLUSTER_STOPPED") { + const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); + if (cluster) { + cluster.startedAt = void 0; + if (this.eventMap.CLUSTER_STOPPED) this.eventMap.CLUSTER_STOPPED(cluster); + cluster.setConnection(void 0); + } + return; + } + if (m2.type == "INSTANCE_STOP") { + this.stopInstance(bridgeConnection); + } + return; + }); + bridgeConnection.onRequest((m2) => { + if (m2.type == "REDIRECT_REQUEST_TO_GUILD") { + const guildID = m2.guildID; + const shardID = ShardingUtil.getShardIDForGuild(guildID, this.getTotalShards()); + const cluster = this.clusterCalculator.getClusterOfShard(shardID); + if (!cluster) { + return Promise.reject(new Error("cluster not found")); + } + if (cluster.connectionStatus != "connected" /* CONNECTED */) { + return Promise.reject(new Error("cluster not connected.")); + } + if (!cluster.connection?.eventManager) { + return Promise.reject(new Error("no connection defined.")); + } + return cluster.connection.eventManager.request({ + type: "REDIRECT_REQUEST_TO_GUILD", + clusterID: cluster.clusterID, + guildID, + data: m2.data + }, 5e3); + } + if (m2.type == "BROADCAST_EVAL") { + const responses = Promise.all( + this.connectedClients.values().map((c) => { + return c.eventManager.request({ + type: "BROADCAST_EVAL", + data: m2.data + }, 5e3); + }) + ); + return new Promise((resolve, reject) => { + responses.then((r) => { + resolve(r.flatMap((f) => f)); + }).catch(reject); + }); + } + if (m2.type == "SELF_CHECK") { + return { + clusterList: [ + ...this.clusterCalculator.getClusterForConnection(bridgeConnection).map((c) => c.clusterID), + ...this.clusterCalculator.getOldClusterForConnection(bridgeConnection).map((c) => c.clusterID) + ] + }; + } + return Promise.reject(new Error("unknown type")); + }); + this.connectedClients.set(connection.id, bridgeConnection); + }); + this.server.on("disconnect", (connection, reason) => { + const closedConnection = this.connectedClients.get(connection.id); + if (!closedConnection) { + return; + } + const clusters = this.clusterCalculator.getClusterForConnection(closedConnection); + for (const cluster of clusters) { + this.clusterCalculator.clearClusterConnection(cluster.clusterID); + } + this.connectedClients.delete(connection.id); + if (this.eventMap.CLIENT_DISCONNECTED) this.eventMap.CLIENT_DISCONNECTED(closedConnection, reason); + }); + this.server.on("message", (message2, connection) => { + this.sendMessageToClient(connection.id, message2); + }); + } + sendMessageToClient(clientId, message2) { + if (!this.connectedClients.has(clientId)) { + return; + } + const client = this.connectedClients.get(clientId); + if (client) { + client.messageReceive(message2); + } + } + getTotalShards() { + return this.shardsPerCluster * this.clusterToStart; + } + on(event, listener) { + this.eventMap[event] = listener; + } + getClusters() { + return this.clusterCalculator.clusterList; + } + async stopAllInstances() { + const instances = Array.from(this.connectedClients.values()); + for (const instance of instances) { + instance.connectionStatus = "pending_stop" /* PENDING_STOP */; + } + for (const instance of instances) { + await this.stopInstance(instance, false); + } + } + async stopAllInstancesWithRestart() { + const instances = Array.from(this.connectedClients.values()); + for (const instance of instances) { + await this.stopInstance(instance); + await new Promise((resolve) => { + setTimeout(async () => { + resolve(); + }, 1e3 * 10); + }); + } + } + async moveCluster(instance, cluster) { + cluster.reclustering(instance); + this.createCluster(instance, cluster, true); + } + async stopInstance(instance, recluster = true) { + if (this.eventMap.CLIENT_STOP) this.eventMap.CLIENT_STOP(instance); + instance.connectionStatus = "pending_stop" /* PENDING_STOP */; + let clusterToSteal; + await instance.eventManager.send({ + type: "INSTANCE_STOP" + }); + if (recluster) { + while ((clusterToSteal = this.clusterCalculator.getClusterForConnection(instance).filter((c) => c.connectionStatus === "connected" /* CONNECTED */ || c.connectionStatus == "starting" /* STARTING */ || c.connectionStatus == "reclustering" /* RECLUSTERING */)[0]) !== void 0) { + if (clusterToSteal.connectionStatus != "connected" /* CONNECTED */) break; + const least = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients); + if (!least) { + if (this.eventMap.ERROR) { + this.eventMap.ERROR("Reclustering failed: No least cluster found."); + } + await instance.eventManager.send({ + type: "CLUSTER_STOP", + data: { + id: clusterToSteal.clusterID + } + }); + clusterToSteal.connection = void 0; + clusterToSteal.connectionStatus = "disconnected" /* DISCONNECTED */; + continue; + } + clusterToSteal.reclustering(least); + if (this.eventMap.CLUSTER_RECLUSTER) { + this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection); + } + this.createCluster(least, clusterToSteal, true); + } + return new Promise((resolve, reject) => { + const interval = setInterval(async () => { + const cluster = this.clusterCalculator.getOldClusterForConnection(instance)[0] || void 0; + if (!cluster) { + clearInterval(interval); + await instance.eventManager.send({ + type: "INSTANCE_STOPPED" + }); + await instance.connection.close("Instance stopped.", false); + resolve(); + return; + } + }, 1e3); + }); + } else { + for (const cluster of this.clusterCalculator.getClusterForConnection(instance)) { + await instance.eventManager.send({ + type: "CLUSTER_STOP", + data: { + id: cluster.clusterID + } + }); + } + await instance.eventManager.send({ + type: "INSTANCE_STOPPED" + }); + await instance.connection.close("Instance stopped.", false); + } + } + sendRequestToGuild(cluster, guildID, data, timeout = 5e3) { + if (!cluster.connection) { + return Promise.reject(new Error("No connection defined for cluster " + cluster.clusterID)); + } + return cluster.connection.eventManager.request({ + type: "REDIRECT_REQUEST_TO_GUILD", + clusterID: cluster.clusterID, + guildID, + data + }, timeout); + } +}; + +// src/cluster/Cluster.ts +var import_os = __toESM(require("os")); +var Cluster = class _Cluster { + instanceID; + clusterID; + shardList = []; + totalShards; + token; + intents; + eventManager; + client; + onSelfDestruct; + eventMap = { + message: void 0, + request: void 0, + CLUSTER_READY: void 0 + }; + constructor(instanceID, clusterID, shardList, totalShards, token, intents) { + this.instanceID = instanceID; + this.clusterID = clusterID; + this.shardList = shardList; + this.totalShards = totalShards; + this.token = token; + this.intents = intents; + this.eventManager = new EventManager((message2) => { + return new Promise((resolve, reject) => { + if (typeof process.send !== "function") { + reject(new Error("Process does not support sending messages")); + return; + } + process.send?.(message2, void 0, void 0, (error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + }, (message2) => { + this._onMessage(message2); + }, (message2) => { + return this._onRequest(message2); + }); + process.on("message", (message2) => { + this.eventManager.receive(message2); + }); + } + static initial() { + const args = process.env; + if (args.SHARD_LIST == void 0 || args.INSTANCE_ID == void 0 || args.TOTAL_SHARDS == void 0 || args.TOKEN == void 0 || args.INTENTS == void 0 || args.CLUSTER_ID == void 0) { + throw new Error("Missing required environment variables"); + } + const shardList = args.SHARD_LIST.split(",").map(Number); + const totalShards = Number(args.TOTAL_SHARDS); + const instanceID = Number(args.INSTANCE_ID); + const clusterID = Number(args.CLUSTER_ID); + const token = args.TOKEN; + const intents = args.INTENTS.split(",").map((i) => i.trim()); + return new _Cluster(instanceID, clusterID, shardList, totalShards, token, intents); + } + triggerReady(guilds, members) { + this.eventManager.send({ + type: "CLUSTER_READY", + id: this.clusterID, + guilds, + members + }); + if (this.eventMap?.CLUSTER_READY) { + this.eventMap?.CLUSTER_READY(); + } + } + triggerError(e) { + this.eventManager.send({ + type: "CLUSTER_ERROR", + id: this.clusterID + }); + } + async wait(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + _onMessage(message2) { + const m2 = message2; + if (m2.type == "CUSTOM" && this.eventMap.message) { + this.eventMap.message(m2.data); + } + } + _onRequest(message) { + const m = message; + if (m.type == "CUSTOM" && this.eventMap.request) { + return new Promise((resolve, reject) => { + this.eventMap.request(m.data, resolve, reject); + }); + } else if (m.type == "CLUSTER_HEARTBEAT") { + const startTime = process.hrtime.bigint(); + const startUsage = process.cpuUsage(); + (async () => { + await this.wait(500); + })(); + const endTime = process.hrtime.bigint(); + const usageDiff = process.cpuUsage(startUsage); + const elapsedTimeUs = Number((endTime - startTime) / 1000n); + const totalCPUTime = usageDiff.user + usageDiff.system; + const cpuCount = import_os.default.cpus().length; + const cpuPercent = totalCPUTime / (elapsedTimeUs * cpuCount) * 100; + let shardPings = []; + try { + const shards = this.client.ws.shards; + if (shards) { + shards.forEach((shard) => { + shardPings.push({ + id: shard.id, + ping: shard.ping, + status: shard.status, + guilds: this.client.guilds.cache.filter((g) => g.shardId === shard.id).size, + members: this.client.guilds.cache.filter((g) => g.shardId === shard.id).reduce((acc, g) => acc + g.memberCount, 0) + }); + this.client.shard?.fetchClientValues("uptime", shard.id).then((values) => { + shardPings[shard.id]["uptime"] = values; + console.log(values); + }).catch((e) => { + }); + }); + } + } catch (_) { + } + return { + cpu: { raw: process.cpuUsage(), cpuPercent: cpuPercent.toFixed(2) }, + memory: { + raw: process.memoryUsage(), + memoryPercent: (process.memoryUsage().heapUsed / process.memoryUsage().heapTotal * 100).toFixed(2) + "%", + usage: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + "MB" + }, + ping: this.client.ws.ping, + shardPings + }; + } else if (m.type == "BROADCAST_EVAL") { + const broadcast = message; + const fn = eval(`(${broadcast.data})`); + const result = fn(this.client); + if (result instanceof Promise) { + return new Promise((resolve, reject) => { + result.then((res) => { + resolve(res); + }).catch((err) => { + reject(err); + }); + }); + } else { + return result; + } + } else if (m.type == "SELF_DESTRUCT") { + if (this.onSelfDestruct) { + this.onSelfDestruct(); + } + } + return void 0; + } + on(event, listener) { + this.eventMap[event] = listener; + } + sendMessage(data) { + this.eventManager.send({ + type: "CUSTOM", + data + }); + } + sendRequest(data, timeout = 5e3) { + return this.eventManager.request({ + type: "CUSTOM", + data + }, timeout); + } + broadcastEval(fn2, timeout = 2e4) { + return this.eventManager.request({ + type: "BROADCAST_EVAL", + data: fn2.toString() + }, timeout); + } + sendMessageToClusterOfGuild(guildID, message2) { + if (this.eventManager) { + this.eventManager.send({ + type: "REDIRECT_MESSAGE_TO_GUILD", + guildID, + data: message2 + }); + } + } + sendRequestToClusterOfGuild(guildID, message2, timeout = 5e3) { + return new Promise((resolve, reject) => { + if (this.eventManager) { + this.eventManager.request({ + type: "REDIRECT_REQUEST_TO_GUILD", + guildID, + data: message2 + }, timeout).then((response) => { + resolve(response); + }).catch((error) => { + reject(error); + }); + } else { + reject(new Error("Event manager is not initialized")); + } + }); + } +}; + +// src/cluster/ClusterProcess.ts +var ClusterProcess = class { + child; + eventManager; + id; + shardList; + totalShards; + status; + createdAt = Date.now(); + _onMessage; + _onRequest; + constructor(id, child, shardList, totalShards) { + this.id = id; + this.child = child; + this.shardList = shardList; + this.totalShards = totalShards; + this.status = "starting"; + this.eventManager = new EventManager((message2) => { + return new Promise((resolve, reject) => { + this.child.send(message2, (error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + }, (message2) => { + if (this._onMessage) { + this._onMessage(message2); + } + }, (message2) => { + if (this._onRequest) { + return this._onRequest(message2); + } + return void 0; + }); + this.child.on("message", (message2) => { + this.eventManager.receive(message2); + }); + this.child.on("exit", () => { + this.eventManager.close("child process exited"); + }); + this.child.on("error", () => { + this.eventManager.close("child process error"); + }); + } + onMessage(callback) { + this._onMessage = callback; + } + onRequest(callback) { + this._onRequest = callback; + } + sendMessage(data) { + this.eventManager.send({ + type: "CUSTOM", + data + }); + } + sendRequest(data, timeout = 5e3) { + return this.eventManager.request({ + type: "CUSTOM", + data + }, timeout); + } +}; + +// src/instance/BotInstance.ts +var import_child_process = require("child_process"); +var BotInstance = class { + entryPoint; + execArgv; + clients = /* @__PURE__ */ new Map(); + constructor(entryPoint, execArgv) { + this.entryPoint = entryPoint; + this.execArgv = execArgv ?? []; + } + eventMap = { + "message": void 0, + "request": void 0, + "PROCESS_KILLED": void 0, + "PROCESS_SELF_DESTRUCT_ERROR": void 0, + "PROCESS_SPAWNED": void 0, + "ERROR": void 0, + "PROCESS_ERROR": void 0, + "CLUSTER_READY": void 0, + "CLUSTER_ERROR": void 0, + "CLUSTER_RECLUSTER": void 0, + "BRIDGE_CONNECTION_ESTABLISHED": void 0, + "BRIDGE_CONNECTION_CLOSED": void 0, + "BRIDGE_CONNECTION_STATUS_CHANGE": void 0, + "INSTANCE_STOP": void 0, + "INSTANCE_STOPPED": void 0, + "SELF_CHECK_SUCCESS": void 0, + "SELF_CHECK_ERROR": void 0, + "SELF_CHECK_RECEIVED": void 0 + }; + startProcess(instanceID, clusterID, shardList, totalShards, token, intents) { + try { + const child = (0, import_child_process.fork)(this.entryPoint, { + env: { + INSTANCE_ID: instanceID.toString(), + CLUSTER_ID: clusterID.toString(), + SHARD_LIST: shardList.join(","), + TOTAL_SHARDS: totalShards.toString(), + TOKEN: token, + INTENTS: intents.join(","), + FORCE_COLOR: "true" + }, + stdio: "inherit", + execArgv: this.execArgv, + silent: false, + detached: true + }); + const client = new ClusterProcess(clusterID, child, shardList, totalShards); + child.stdout?.on("data", (data) => { + process.stdout.write(data); + }); + child.stderr?.on("data", (data) => { + process.stderr.write(data); + }); + child.on("spawn", () => { + if (this.eventMap.PROCESS_SPAWNED) this.eventMap.PROCESS_SPAWNED(client); + this.setClusterSpawned(client); + this.clients.set(clusterID, client); + client.onMessage((message2) => { + this.onMessage(client, message2); + }); + client.onRequest((message2) => { + return this.onRequest(client, message2); + }); + }); + child.on("error", (err) => { + if (this.eventMap.PROCESS_ERROR) this.eventMap.PROCESS_ERROR(client, err); + }); + child.on("exit", (err) => { + if (client.status !== "stopped") { + client.status = "stopped"; + this.killProcess(client, `Process exited: ${err?.message}`); + } + }); + } catch (error) { + throw new Error(`Failed to start process for cluster ${clusterID}: ${error instanceof Error ? error.message : String(error)}`); + } + } + killProcess(client, reason) { + client.status = "stopped"; + client.eventManager.request({ + type: "SELF_DESTRUCT", + reason + }, 5e3).catch(() => { + if (this.eventMap.PROCESS_SELF_DESTRUCT_ERROR) this.eventMap.PROCESS_SELF_DESTRUCT_ERROR(client, reason, "Cluster didnt respond to shot-call."); + }).finally(() => { + if (client.child && client.child.pid) { + if (client.child.kill("SIGKILL")) { + if (this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, true); + } else { + if (this.eventMap.ERROR) this.eventMap.ERROR(`Failed to kill process for cluster ${client.id}`); + client.child.kill("SIGKILL"); + } + try { + process.kill(-client.child.pid); + } catch { + } + } else { + if (this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, false); + } + this.clients.delete(client.id); + this.setClusterStopped(client, reason); + }); + } + onMessage(client, message2) { + if (message2.type === "CLUSTER_READY") { + client.status = "running"; + if (this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(client); + this.setClusterReady(client, message2.guilds || 0, message2.members || 0); + } + if (message2.type === "CLUSTER_ERROR") { + client.status = "stopped"; + if (this.eventMap.CLUSTER_ERROR) this.eventMap.CLUSTER_ERROR(client, message2.error); + this.killProcess(client, "Cluster error: " + message2.error); + } + if (message2.type == "CUSTOM" && this.eventMap.message) { + this.eventMap.message(client, message2.data); + } + } + on(event, listener) { + this.eventMap[event] = listener; + } + sendRequestToClusterOfGuild(guildID, message2, timeout = 5e3) { + return new Promise((resolve, reject) => { + for (const client of this.clients.values()) { + const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); + if (client.shardList.includes(shardID)) { + client.eventManager.request({ + type: "CUSTOM", + data: message2 + }, timeout).then(resolve).catch(reject); + return; + } + } + reject(new Error(`No cluster found for guild ${guildID}`)); + }); + } + sendRequestToCluster(cluster, message2, timeout = 5e3) { + return new Promise((resolve, reject) => { + cluster.eventManager.request({ + type: "CUSTOM", + data: message2 + }, timeout).then(resolve).catch(reject); + return; + }); + } +}; + +// src/instance/ManagedInstance.ts +var import_net_ipc2 = require("net-ipc"); +var BridgeConnectionStatus = /* @__PURE__ */ ((BridgeConnectionStatus2) => { + BridgeConnectionStatus2[BridgeConnectionStatus2["CONNECTED"] = 0] = "CONNECTED"; + BridgeConnectionStatus2[BridgeConnectionStatus2["DISCONNECTED"] = 1] = "DISCONNECTED"; + return BridgeConnectionStatus2; +})(BridgeConnectionStatus || {}); +var ManagedInstance = class extends BotInstance { + host; + port; + instanceID; + eventManager; + connectionStatus = 1 /* DISCONNECTED */; + data; + dev = false; + constructor(entryPoint, host, port, instanceID, data, execArgv, dev) { + super(entryPoint, execArgv); + this.host = host; + this.port = port; + this.instanceID = instanceID; + this.data = data; + this.dev = dev || false; + } + start() { + const client = new import_net_ipc2.Client({ + host: this.host, + port: this.port, + reconnect: true, + retries: 100 + }); + this.eventManager = new EventManager((message2) => { + if (client.status == 3) { + return client.send(message2); + } + return Promise.reject(new Error("Client is not ready to send messages")); + }, (message2) => { + const m2 = message2; + if (m2.type == "CLUSTER_CREATE") { + this.onClusterCreate(m2.data); + } else if (m2.type == "CLUSTER_STOP") { + this.onClusterStop(m2.data); + } else if (m2.type == "CLUSTER_RECLUSTER") { + this.onClusterRecluster(m2.data); + } else if (m2.type == "INSTANCE_STOP") { + if (this.eventMap.INSTANCE_STOP) { + this.eventMap.INSTANCE_STOP(); + } + } else if (m2.type == "INSTANCE_STOPPED") { + if (this.eventMap.INSTANCE_STOPPED) { + this.eventMap.INSTANCE_STOPPED(); + } + } + }, (message2) => { + return this.onBridgeRequest(message2); + }); + setInterval(() => { + if (this.connectionStatus == 0 /* CONNECTED */) { + this.selfCheck(); + } + }, 2500); + client.connect({ + id: this.instanceID, + dev: this.dev, + data: this.data + }).then((_) => { + if (this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED(); + this.connectionStatus = 0 /* CONNECTED */; + client.on("message", (message2) => { + this.eventManager?.receive(message2); + }); + client.on("close", (reason) => { + if (this.eventMap.BRIDGE_CONNECTION_CLOSED) this.eventMap.BRIDGE_CONNECTION_CLOSED(reason); + if (this.connectionStatus == 0 /* CONNECTED */) { + this.clients.forEach((client2) => { + this.killProcess(client2, "Bridge connection closed"); + }); + } + this.connectionStatus = 1 /* DISCONNECTED */; + }); + client.on("status", (status) => { + if (this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE) this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE(status); + if (status == 4) { + if (this.connectionStatus == 0 /* CONNECTED */) { + this.clients.forEach((client2) => { + this.killProcess(client2, "Bridge connection closed"); + }); + } + this.connectionStatus = 1 /* DISCONNECTED */; + } else if (status == 3) { + this.connectionStatus = 0 /* CONNECTED */; + if (this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED(); + } + }); + }); + } + selfCheck() { + this.eventManager.request({ + type: "SELF_CHECK" + }, 1e3 * 60).then((r) => { + const response = r; + if (this.eventMap.SELF_CHECK_RECEIVED) { + this.eventMap.SELF_CHECK_RECEIVED(response); + } + const startingClusters = this.clients.values().filter((c) => c.status == "starting").toArray(); + startingClusters.forEach((c) => { + if (Date.now() - c.createdAt > 10 * 60 * 1e3) { + this.killProcess(c, "Cluster took too long to start"); + } + }); + const wrongClusters = this.clients.values().filter((c) => !response.clusterList.includes(c.id)).toArray(); + if (wrongClusters.length > 0) { + if (this.eventMap.SELF_CHECK_ERROR) { + this.eventMap.SELF_CHECK_ERROR(`Self check found wrong clusters: ${wrongClusters.map((c) => c.id).join(", ")}`); + } + wrongClusters.forEach((c) => { + this.killProcess(c, "Self check found wrong cluster"); + }); + } else { + if (this.eventMap.SELF_CHECK_SUCCESS) { + this.eventMap.SELF_CHECK_SUCCESS(); + } + } + }).catch((err) => { + if (this.eventMap.SELF_CHECK_ERROR) { + this.eventMap.SELF_CHECK_ERROR(`Self check failed: ${err}`); + } + }); + } + setClusterStopped(client, reason) { + this.eventManager?.send({ + type: "CLUSTER_STOPPED", + data: { + id: client.id, + reason + } + }).catch(() => { + return null; + }); + } + setClusterReady(client, guilds, members) { + this.eventManager?.send({ + type: "CLUSTER_READY", + data: { + id: client.id, + guilds, + members + } + }); + } + setClusterSpawned(client) { + this.eventManager?.send({ + type: "CLUSTER_SPAWNED", + data: { + id: client.id + } + }); + } + onClusterCreate(message2) { + const m2 = message2; + if (this.clients.has(m2.clusterID)) { + this.eventManager?.send({ + type: "CLUSTER_STOPPED", + data: { + id: m2.clusterID, + reason: "Cluster already exists" + } + }).catch(() => { + return null; + }); + return; + } + this.startProcess(this.instanceID, m2.clusterID, m2.shardList, m2.totalShards, m2.token, m2.intents); + } + onClusterStop(message2) { + const m2 = message2; + const cluster = this.clients.get(m2.id); + if (cluster) { + this.killProcess(cluster, `Request to stop cluster ${m2.id}`); + } + } + onClusterRecluster(message2) { + const m2 = message2; + const cluster = this.clients.get(m2.clusterID); + if (this.eventMap.CLUSTER_RECLUSTER && cluster) { + this.eventMap.CLUSTER_RECLUSTER(cluster); + } + } + onRequest(client, message2) { + if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { + const guildID = message2.guildID; + const data = message2.data; + const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); + if (client.shardList.includes(shardID)) { + return client.eventManager.request({ + type: "CUSTOM", + data + }, 5e3); + } else { + return this.eventManager.request({ + type: "REDIRECT_REQUEST_TO_GUILD", + guildID, + data + }, 5e3); + } + } + if (message2.type == "BROADCAST_EVAL") { + return this.eventManager.request({ + type: "BROADCAST_EVAL", + data: message2.data + }, 5e3); + } + if (message2.type == "CUSTOM" && this.eventMap.request) { + return new Promise((resolve, reject) => { + this.eventMap.request(client, message2.data, resolve, reject); + }); + } + return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); + } + onBridgeRequest(message2) { + if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { + const clusterID = message2.clusterID; + const data = message2.data; + const cluster = this.clients.get(clusterID); + if (cluster) { + return cluster.eventManager.request({ + type: "CUSTOM", + data + }, 5e3); + } else { + return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`)); + } + } else if (message2.type == "CLUSTER_HEARTBEAT") { + const clusterID = message2.data.clusterID; + const cluster = this.clients.get(clusterID); + if (cluster) { + return new Promise((resolve, reject) => { + cluster.eventManager.request({ + type: "CLUSTER_HEARTBEAT" + }, 15e3).then((r) => { + resolve(r); + }).catch((err) => { + reject(err); + }); + }); + } else { + return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`)); + } + } else if (message2.type == "BROADCAST_EVAL") { + return Promise.all(this.clients.values().filter((c) => c.status == "running").map((c) => { + return c.eventManager.request({ + type: "BROADCAST_EVAL", + data: message2.data + }, 5e3); + })); + } + return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); + } + stopInstance() { + this.eventManager?.send({ + type: "INSTANCE_STOP" + }); + } +}; + +// src/instance/StandaloneInstance.ts +var StandaloneInstance = class extends BotInstance { + totalClusters; + shardsPerCluster; + token; + intents; + constructor(entryPoint, shardsPerCluster, totalClusters, token, intents, execArgv) { + super(entryPoint, execArgv); + this.shardsPerCluster = shardsPerCluster; + this.totalClusters = totalClusters; + this.token = token; + this.intents = intents; + } + get totalShards() { + return this.shardsPerCluster * this.totalClusters; + } + calculateClusters() { + const clusters = {}; + for (let i = 0; i < this.totalClusters; i++) { + clusters[i] = []; + for (let j = 0; j < this.shardsPerCluster; j++) { + clusters[i].push(i * this.shardsPerCluster + j); + } + } + return clusters; + } + start() { + const clusters = this.calculateClusters(); + for (const [id, shardList] of Object.entries(clusters)) { + this.startProcess(1, Number(id), shardList, this.totalShards, this.token, this.intents); + } + } + setClusterStopped(client, reason) { + this.clients.delete(client.id); + this.restartProcess(client); + } + setClusterReady(client) { + } + setClusterSpawned(client) { + } + restartProcess(client) { + this.startProcess(1, client.id, client.shardList, this.totalShards, this.token, this.intents); + } + onRequest(client, message2) { + if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { + const guildID = message2.guildID; + const data = message2.data; + const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); + if (client.shardList.includes(shardID)) { + return client.eventManager.request({ + type: "CUSTOM", + data + }, 5e3); + } else { + return Promise.reject(new Error(`Shard ID ${shardID} not found in cluster ${client.id} for guild ${guildID}`)); + } + } + if (message2.type == "BROADCAST_EVAL") { + return Promise.all( + this.clients.values().map((c) => { + return c.eventManager.request({ + type: "BROADCAST_EVAL", + data: message2.data + }, 5e3); + }) + ); + } + if (message2.type == "CUSTOM" && this.eventMap.request) { + return new Promise((resolve, reject) => { + this.eventMap.request(client, message2.data, resolve, reject); + }); + } + return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); + } +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + BotInstance, + Bridge, + BridgeClientCluster, + BridgeClientClusterConnectionStatus, + BridgeClientConnection, + BridgeClientConnectionStatus, + BridgeConnectionStatus, + Cluster, + ClusterCalculator, + ClusterProcess, + EventManager, + ManagedInstance, + ShardingUtil, + StandaloneInstance +}); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..cbf0ec8 --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/index.ts","../src/bridge/BridgeClientCluster.ts","../src/general/EventManager.ts","../src/bridge/BridgeClientConnection.ts","../src/bridge/Bridge.ts","../src/bridge/ClusterCalculator.ts","../src/general/ShardingUtil.ts","../src/cluster/Cluster.ts","../src/cluster/ClusterProcess.ts","../src/instance/BotInstance.ts","../src/instance/ManagedInstance.ts","../src/instance/StandaloneInstance.ts"],"sourcesContent":["export * from './bridge/BridgeClientCluster';\nexport * from './bridge/BridgeClientConnection';\nexport * from './bridge/Bridge';\nexport * from './bridge/ClusterCalculator';\n\nexport * from './cluster/Cluster';\nexport * from './cluster/ClusterProcess';\n\nexport * from './general/EventManager';\nexport * from './general/ShardingUtil';\nexport * from './general/EventPayload';\n\nexport * from './instance/BotInstance';\nexport * from './instance/ManagedInstance';\nexport * from './instance/StandaloneInstance';","import {BridgeClientConnection} from \"./BridgeClientConnection\";\n\nexport enum BridgeClientClusterConnectionStatus {\n REQUESTING = 'requesting',\n STARTING = 'starting',\n CONNECTED = 'connected',\n RECLUSTERING = 'reclustering',\n DISCONNECTED = 'disconnected',\n}\n\nexport class BridgeClientCluster {\n public readonly clusterID: number;\n public readonly shardList: number[];\n public connectionStatus: BridgeClientClusterConnectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n\n public connection?: BridgeClientConnection;\n\n public oldConnection?: BridgeClientConnection;\n\n public missedHeartbeats: number = 0;\n\n public heartbeatResponse?: HeartbeatResponse;\n\n public heartbeatPending = false;\n\n public startedAt?: number;\n\n constructor(clusterID: number, shardList: number[]) {\n this.clusterID = clusterID;\n this.shardList = shardList;\n }\n\n setConnection(connection?: BridgeClientConnection): void {\n if(connection == undefined){\n this.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n this.connection = undefined;\n return;\n }\n\n if (this.connection) {\n throw new Error(`Connection already set for cluster ${this.clusterID}`);\n }\n\n this.connectionStatus = BridgeClientClusterConnectionStatus.REQUESTING;\n this.connection = connection;\n }\n\n setOldConnection(connection?: BridgeClientConnection): void {\n this.oldConnection = connection;\n }\n\n isUsed(): boolean {\n return this.connection != undefined && this.connectionStatus !== BridgeClientClusterConnectionStatus.DISCONNECTED;\n }\n\n reclustering(connection: BridgeClientConnection): void {\n this.connectionStatus = BridgeClientClusterConnectionStatus.RECLUSTERING;\n this.oldConnection = this.connection;\n this.connection = connection;\n }\n\n addMissedHeartbeat(): void {\n this.missedHeartbeats++;\n }\n\n removeMissedHeartbeat(): void {\n if (this.missedHeartbeats > 0) {\n this.missedHeartbeats--;\n }\n }\n\n resetMissedHeartbeats(): void {\n this.missedHeartbeats = 0;\n }\n}\n\nexport type HeartbeatResponse = {\n cpu: {\n raw: {\n user: number,\n system: number,\n }\n cpuPercent: string\n },\n memory: {\n raw: {\n rss: number,\n heapTotal: number,\n heapUsed: number,\n external: number,\n arrayBuffers: number,\n },\n memoryPercent: string\n usage: number\n },\n ping: number,\n shardPings: {\n id: number,\n ping: number,\n status: number,\n guilds: number,\n members: number\n }[]\n}","import {EventPayload} from \"./EventPayload\";\n\nexport class EventManager {\n\n private pendingPayloads = new Map void;\n reject: (error: unknown) => void;\n }>();\n\n // Track per-request timeout handles so we can clear them on resolve/reject\n private pendingTimeouts = new Map>();\n\n private readonly _send: (payload: EventPayload) => Promise;\n\n private readonly _on: (payload: unknown) => void;\n\n private readonly _request: (payload: unknown) => unknown;\n\n constructor(send: (payload: EventPayload) => Promise, on: (message: unknown) => void, request: (message: unknown) => unknown) {\n this._send = send;\n this._on = on;\n this._request = request\n }\n\n async send(data: unknown) {\n return this._send({\n id: crypto.randomUUID(),\n type: 'message',\n data: data\n });\n }\n\n async request(payload: unknown, timeout: number): Promise {\n const id = crypto.randomUUID();\n\n return new Promise((resolve, reject) => {\n this._send({\n id: id,\n type: 'request',\n data: payload\n });\n\n this.pendingPayloads.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject\n });\n\n const t = setTimeout(() => {\n if (this.pendingPayloads.has(id)) {\n this.pendingPayloads.delete(id);\n this.pendingTimeouts.delete(id);\n reject({\n error: `Request with id ${id} timed out`,\n });\n }\n }, timeout);\n this.pendingTimeouts.set(id, t);\n })\n }\n\n receive(possiblePayload: unknown) {\n if (typeof possiblePayload !== 'object' || possiblePayload === null) {\n return;\n }\n\n const payload = possiblePayload as EventPayload;\n\n if (!payload.id || !payload.type) {\n return;\n }\n\n if (payload.type === 'message') {\n this._on(payload.data);\n return;\n }\n\n if (payload.type === 'response') {\n // Handle requests\n const resolve = this.pendingPayloads.get(payload.id)?.resolve;\n if (resolve) {\n resolve(payload.data);\n this.pendingPayloads.delete(payload.id);\n const to = this.pendingTimeouts.get(payload.id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(payload.id);\n }\n return;\n }\n\n if (payload.type === 'response_error') {\n // Handle requests\n const reject = this.pendingPayloads.get(payload.id)?.reject;\n if (reject) {\n reject(payload.data);\n this.pendingPayloads.delete(payload.id);\n const to = this.pendingTimeouts.get(payload.id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(payload.id);\n }\n return;\n }\n\n if (payload.type === 'request') {\n // Handle requests\n const data = this._request(payload.data);\n if(data instanceof Promise) {\n data.then((result) => {\n this._send({\n id: payload.id,\n type: 'response',\n data: result\n });\n }).catch((error) => {\n this._send({\n id: payload.id,\n type: 'response_error',\n data: error\n });\n });\n } else {\n this._send({\n id: payload.id,\n type: 'response',\n data: data\n });\n }\n return;\n }\n }\n\n // Reject and clear all pending requests to avoid memory leaks when a connection/process closes\n close(reason?: string) {\n if (this.pendingPayloads.size === 0 && this.pendingTimeouts.size === 0) return;\n const err = { error: reason || 'EventManager closed' };\n for (const [id, handlers] of this.pendingPayloads.entries()) {\n try { handlers.reject(err); } catch (_) { /* ignore */ }\n this.pendingPayloads.delete(id);\n const to = this.pendingTimeouts.get(id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(id);\n }\n // In case there are any stray timeouts with no pending payload\n for (const to of this.pendingTimeouts.values()) {\n clearTimeout(to);\n }\n this.pendingTimeouts.clear();\n }\n}\n\n","import {EventManager} from \"../general/EventManager\";\nimport {Connection} from \"net-ipc\";\n\nexport enum BridgeClientConnectionStatus {\n READY = 'ready',\n PENDING_STOP = 'pending_stop',\n}\nexport class BridgeClientConnection {\n public readonly instanceID: number;\n public readonly eventManager: EventManager;\n public readonly connection: Connection;\n public readonly data: unknown;\n public connectionStatus: BridgeClientConnectionStatus = BridgeClientConnectionStatus.READY;\n public readonly dev: boolean = false;\n public readonly establishedAt: number = Date.now();\n\n private _onMessage?: (message: unknown) => void;\n private _onRequest?: (message: unknown) => unknown;\n\n constructor(instanceID: number, connection: Connection, data: unknown, dev: boolean) {\n this.instanceID = instanceID;\n this.connection = connection;\n this.data = data;\n this.dev = dev || false;\n this.eventManager = new EventManager((message) => {\n if(!this.connection?.connection?.closed){\n return this.connection.send(message);\n }\n return Promise.reject(new Error('Connection is closed, cannot send message'));\n }, (message) => {\n if (this._onMessage) {\n this._onMessage(message);\n }\n }, (message) => {\n if (this._onRequest) {\n return this._onRequest(message);\n }\n return undefined;\n })\n }\n\n messageReceive(message: any) {\n this.eventManager.receive(message);\n }\n\n onRequest(callback: (message: unknown) => unknown) {\n this._onRequest = callback;\n }\n\n onMessage(callback: (message: unknown) => void) {\n this._onMessage = callback;\n }\n}","import {Server} from 'net-ipc';\nimport {BridgeClientConnection, BridgeClientConnectionStatus} from \"./BridgeClientConnection\";\nimport {GatewayIntentsString, Snowflake} from \"discord.js\";\nimport {ClusterCalculator} from \"./ClusterCalculator\";\nimport {BridgeClientCluster, BridgeClientClusterConnectionStatus, HeartbeatResponse} from \"./BridgeClientCluster\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport class Bridge {\n public readonly port: number;\n public readonly server: Server;\n public readonly connectedClients: Map = new Map();\n private readonly token: string;\n private readonly intents: GatewayIntentsString[];\n private readonly shardsPerCluster: number = 1;\n private readonly clusterToStart: number = 1\n private readonly reclusteringTimeoutInMs: number;\n\n private readonly clusterCalculator: ClusterCalculator;\n\n private readonly eventMap: BridgeEventListeners = {\n CLUSTER_READY: undefined, CLUSTER_HEARTBEAT_FAILED: undefined,\n CLUSTER_STOPPED: undefined, CLIENT_CONNECTED: undefined, CLIENT_DISCONNECTED: undefined,\n CLUSTER_SPAWNED: undefined, CLUSTER_RECLUSTER: undefined, ERROR: undefined,\n CLIENT_STOP: undefined\n }\n\n constructor(port: number, token: string, intents: GatewayIntentsString[], shardsPerCluster: number, clusterToStart: number, reclusteringTimeoutInMs: number) {\n this.port = port;\n this.token = token;\n this.intents = intents;\n this.clusterToStart = clusterToStart;\n this.shardsPerCluster = shardsPerCluster;\n this.reclusteringTimeoutInMs = reclusteringTimeoutInMs;\n\n this.clusterCalculator = new ClusterCalculator(this.clusterToStart, this.shardsPerCluster);\n\n this.server = new Server({\n port: this.port,\n })\n }\n\n public start(): void {\n this.server.start().then(() => {\n this.startListening();\n })\n\n this.interval();\n }\n\n private interval(): void {\n setInterval(() => {\n this.checkCreate();\n this.checkRecluster();\n this.heartbeat();\n }, 5000)\n }\n\n private checkRecluster(): void {\n // check if all clusters are used\n const up = this.clusterCalculator.checkAllClustersConnected()\n if (!up) {\n return;\n }\n\n const connectedClients: BridgeClientConnection[] = this.connectedClients.values()\n .filter(c => c.connectionStatus == BridgeClientConnectionStatus.READY)\n .filter(c => !c.dev)\n .filter(c => c.establishedAt + this.reclusteringTimeoutInMs < Date.now())\n .toArray();\n\n const {most, least} = this.clusterCalculator.findMostAndLeastClustersForConnections(connectedClients);\n if (most) {\n const clusterToSteal = this.clusterCalculator.getClusterForConnection(most)[0] || undefined;\n if (least && clusterToSteal) {\n clusterToSteal.reclustering(least);\n\n if(this.eventMap.CLUSTER_RECLUSTER) this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection!);\n this.createCluster(least, clusterToSteal, true);\n\n return;\n }\n }\n }\n\n private heartbeat(): void {\n const clusters = this.clusterCalculator.clusterList;\n\n clusters.forEach((cluster) => {\n if(cluster.connection && cluster.connectionStatus == BridgeClientClusterConnectionStatus.CONNECTED && !cluster.heartbeatPending) {\n cluster.heartbeatPending = true;\n cluster.connection.eventManager.request({\n type: 'CLUSTER_HEARTBEAT',\n data: {\n clusterID: cluster.clusterID\n }\n }, 20000).then((r) => {\n cluster.removeMissedHeartbeat();\n cluster.heartbeatResponse = r;\n }).catch((err) => {\n if(this.eventMap.CLUSTER_HEARTBEAT_FAILED) this.eventMap.CLUSTER_HEARTBEAT_FAILED(cluster, err)\n cluster.addMissedHeartbeat()\n\n if(cluster.missedHeartbeats > 7 && !cluster.connection?.dev){\n cluster.connection?.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n cluster.resetMissedHeartbeats()\n }\n }).finally(() => {\n cluster.heartbeatPending = false;\n })\n }\n });\n }\n\n private checkCreate(): void {\n const optionalCluster = this.clusterCalculator.getNextCluster();\n\n if (!optionalCluster) {\n return;\n }\n\n const lowestLoadClient = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients);\n if (!lowestLoadClient) {\n return;\n }\n\n this.createCluster(lowestLoadClient, optionalCluster)\n }\n\n private createCluster(connection: BridgeClientConnection, cluster: BridgeClientCluster, recluster = false) {\n cluster.resetMissedHeartbeats()\n cluster.heartbeatResponse = undefined;\n if (!recluster) {\n cluster.setConnection(connection)\n } else {\n cluster.oldConnection?.eventManager.send({\n type: 'CLUSTER_RECLUSTER',\n data: {\n clusterID: cluster.clusterID\n }\n })\n }\n if(this.eventMap.CLUSTER_SPAWNED) this.eventMap.CLUSTER_SPAWNED(cluster, connection)\n connection.eventManager.send({\n type: 'CLUSTER_CREATE',\n data: {\n clusterID: cluster.clusterID,\n instanceID: connection.instanceID,\n totalShards: this.getTotalShards(),\n shardList: cluster.shardList,\n token: this.token,\n intents: this.intents\n }\n });\n }\n\n public startListening(): void {\n this.server.on('connect', (connection, payload) => {\n const id = payload?.id;\n const data = payload.data as unknown;\n const dev = payload?.dev || false;\n if (!id) {\n connection.close('Invalid payload', false);\n return;\n }\n\n if (this.connectedClients.values().some(client => client.instanceID === id)) {\n connection.close('Already connected', false);\n return;\n }\n\n const bridgeConnection = new BridgeClientConnection(payload.id, connection, data, dev);\n if(this.eventMap.CLIENT_CONNECTED) this.eventMap.CLIENT_CONNECTED(bridgeConnection);\n\n bridgeConnection.onMessage((m: any) => {\n if (m.type == 'CLUSTER_SPAWNED') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.STARTING;\n }\n return;\n }\n\n if (m.type == 'CLUSTER_READY') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.startedAt = Date.now();\n if(this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(cluster, m.data.guilds || 0, m.data.members || 0);\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.CONNECTED;\n if (cluster.oldConnection) {\n cluster.oldConnection.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n cluster.oldConnection = undefined;\n }\n }\n return;\n }\n\n if (m.type == 'CLUSTER_STOPPED') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.startedAt = undefined;\n if(this.eventMap.CLUSTER_STOPPED) this.eventMap.CLUSTER_STOPPED(cluster);\n cluster.setConnection(undefined);\n }\n return;\n }\n\n if(m.type == \"INSTANCE_STOP\") {\n this.stopInstance(bridgeConnection);\n }\n\n return;\n })\n\n bridgeConnection.onRequest((m: any) => {\n if(m.type == 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = m.guildID;\n const shardID = ShardingUtil.getShardIDForGuild(guildID, this.getTotalShards());\n const cluster = this.clusterCalculator.getClusterOfShard(shardID);\n if(!cluster){\n return Promise.reject(new Error(\"cluster not found\"))\n }\n if(cluster.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED){\n return Promise.reject(new Error(\"cluster not connected.\"))\n }\n\n if(!cluster.connection?.eventManager){\n return Promise.reject(new Error(\"no connection defined.\"))\n }\n\n return cluster.connection.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n clusterID: cluster.clusterID,\n guildID: guildID,\n data: m.data\n }, 5000)\n }\n\n if(m.type == 'BROADCAST_EVAL') {\n const responses = Promise.all(\n this.connectedClients.values().map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: m.data,\n }, 5000);\n })\n )\n return new Promise((resolve, reject) => {\n responses.then((r) => {\n resolve(r.flatMap(f => f))\n }).catch(reject);\n })\n }\n\n if(m.type == 'SELF_CHECK') {\n return {\n clusterList: [\n ...this.clusterCalculator.getClusterForConnection(bridgeConnection).map(c => c.clusterID),\n ...this.clusterCalculator.getOldClusterForConnection(bridgeConnection).map(c => c.clusterID)\n ]\n }\n }\n\n return Promise.reject(new Error(\"unknown type\"))\n })\n\n this.connectedClients.set(connection.id, bridgeConnection)\n });\n\n this.server.on('disconnect', (connection, reason) => {\n const closedConnection = this.connectedClients.get(connection.id);\n if (!closedConnection) {\n return;\n }\n\n const clusters = this.clusterCalculator.getClusterForConnection(closedConnection);\n for (const cluster of clusters) {\n this.clusterCalculator.clearClusterConnection(cluster.clusterID);\n }\n\n this.connectedClients.delete(connection.id);\n if(this.eventMap.CLIENT_DISCONNECTED) this.eventMap.CLIENT_DISCONNECTED(closedConnection, reason);\n });\n\n this.server.on(\"message\", (message, connection) => {\n this.sendMessageToClient(connection.id, message);\n })\n }\n\n sendMessageToClient(clientId: string, message: unknown): void {\n if (!this.connectedClients.has(clientId)) {\n return;\n }\n\n const client = this.connectedClients.get(clientId);\n if (client) {\n client.messageReceive(message);\n }\n }\n\n private getTotalShards() {\n return this.shardsPerCluster * this.clusterToStart;\n }\n\n\n public on(event: K, listener: BridgeEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public getClusters() {\n return this.clusterCalculator.clusterList;\n }\n\n async stopAllInstances() {\n const instances = Array.from(this.connectedClients.values());\n for (const instance of instances) {\n instance.connectionStatus = BridgeClientConnectionStatus.PENDING_STOP;\n }\n\n for (const instance of instances) {\n await this.stopInstance(instance, false);\n }\n }\n\n async stopAllInstancesWithRestart() {\n const instances = Array.from(this.connectedClients.values());\n\n for (const instance of instances) {\n await this.stopInstance(instance);\n await new Promise((resolve) => {\n setTimeout(async () => {\n resolve();\n }, 1000 * 10);\n })\n }\n }\n\n async moveCluster(instance: BridgeClientConnection, cluster: BridgeClientCluster) {\n cluster.reclustering(instance);\n\n this.createCluster(instance, cluster, true);\n }\n\n async stopInstance(instance: BridgeClientConnection, recluster = true) {\n if(this.eventMap.CLIENT_STOP) this.eventMap.CLIENT_STOP(instance);\n instance.connectionStatus = BridgeClientConnectionStatus.PENDING_STOP;\n\n let clusterToSteal: BridgeClientCluster | undefined;\n\n await instance.eventManager.send({\n type: 'INSTANCE_STOP'\n });\n\n if(recluster) {\n while ((clusterToSteal = this.clusterCalculator.getClusterForConnection(instance).filter(c =>\n c.connectionStatus === BridgeClientClusterConnectionStatus.CONNECTED ||\n c.connectionStatus == BridgeClientClusterConnectionStatus.STARTING ||\n c.connectionStatus == BridgeClientClusterConnectionStatus.RECLUSTERING)[0]) !== undefined) {\n // skip if the cluster is not connected\n if(clusterToSteal.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED) break;\n\n const least = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients);\n if (!least) {\n if (this.eventMap.ERROR) {\n this.eventMap.ERROR(\"Reclustering failed: No least cluster found.\");\n }\n await instance.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: clusterToSteal.clusterID\n }\n });\n clusterToSteal.connection = undefined;\n clusterToSteal.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n continue;\n }\n\n clusterToSteal.reclustering(least);\n\n if (this.eventMap.CLUSTER_RECLUSTER) {\n this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection!);\n }\n\n this.createCluster(least, clusterToSteal, true);\n }\n\n return new Promise((resolve, reject) => {\n const interval = setInterval(async () => {\n const cluster = this.clusterCalculator.getOldClusterForConnection(instance)[0] || undefined;\n if (!cluster) {\n clearInterval(interval);\n await instance.eventManager.send({\n type: 'INSTANCE_STOPPED'\n })\n await instance.connection.close(\"Instance stopped.\", false);\n resolve();\n return;\n }\n }, 1000);\n })\n } else {\n for(const cluster of this.clusterCalculator.getClusterForConnection(instance)) {\n await instance.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n }\n\n\n await instance.eventManager.send({\n type: 'INSTANCE_STOPPED'\n })\n await instance.connection.close(\"Instance stopped.\", false);\n }\n }\n\n sendRequestToGuild(cluster: BridgeClientCluster, guildID: Snowflake, data: unknown, timeout = 5000): Promise {\n if(!cluster.connection){\n return Promise.reject(new Error(\"No connection defined for cluster \" + cluster.clusterID));\n }\n\n return cluster.connection.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n clusterID: cluster.clusterID,\n guildID: guildID,\n data: data\n }, timeout);\n }\n}\n\n\n\nexport type BridgeEventListeners = {\n 'CLUSTER_READY': ((cluster: BridgeClientCluster, guilds: number, members: number) => void) | undefined,\n 'CLUSTER_STOPPED': ((cluster: BridgeClientCluster) => void) | undefined,\n 'CLUSTER_SPAWNED': ((cluster: BridgeClientCluster, connection: BridgeClientConnection) => void) | undefined,\n 'CLUSTER_RECLUSTER': ((cluster: BridgeClientCluster, newConnection: BridgeClientConnection, oldConnection: BridgeClientConnection) => void) | undefined,\n 'CLUSTER_HEARTBEAT_FAILED': ((cluster: BridgeClientCluster, error: unknown) => void) | undefined,\n 'CLIENT_CONNECTED': ((client: BridgeClientConnection) => void) | undefined,\n 'CLIENT_DISCONNECTED': ((client: BridgeClientConnection, reason: string) => void) | undefined,\n 'ERROR': ((error: string) => void) | undefined,\n 'CLIENT_STOP': ((instance: BridgeClientConnection) => void) | undefined\n};","import {BridgeClientCluster, BridgeClientClusterConnectionStatus} from \"./BridgeClientCluster\";\nimport {BridgeClientConnection, BridgeClientConnectionStatus} from \"./BridgeClientConnection\";\n\n/**\n * Manages the calculation and distribution of clusters for a Discord bot sharding system.\n * This class is responsible for creating clusters with their assigned shards,\n * tracking which clusters are in use, and providing methods to retrieve available clusters.\n */\nexport class ClusterCalculator {\n /** The total number of clusters to initialize */\n private readonly clusterToStart: number;\n\n /** The number of shards that each cluster will manage */\n private readonly shardsPerCluster: number;\n\n /** List of all clusters managed by this calculator */\n public readonly clusterList: BridgeClientCluster[]= [];\n\n /**\n * Creates a new ClusterCalculator and initializes the clusters.\n * \n * @param clusterToStart - The number of clusters to create\n * @param shardsPerCluster - The number of shards each cluster will manage\n */\n constructor(clusterToStart: number, shardsPerCluster: number) {\n this.shardsPerCluster = shardsPerCluster;\n this.clusterToStart = clusterToStart;\n\n this.calculateClusters();\n }\n\n /**\n * Calculates and initializes all clusters with their assigned shards.\n * Each cluster is assigned a sequential range of shard IDs based on its cluster index.\n */\n private calculateClusters(): void {\n const clusters: Map = new Map();\n for (let i = 0; i < this.clusterToStart; i++) {\n clusters.set(i, []);\n for (let j = 0; j < this.shardsPerCluster; j++) {\n clusters.get(i)?.push(i * this.shardsPerCluster + j);\n }\n }\n\n for (let [clusterIndex, clusterShards] of clusters.entries()) {\n this.clusterList.push(new BridgeClientCluster(clusterIndex, clusterShards));\n }\n }\n\n /**\n * Retrieves the next available (unused) cluster and marks it as used.\n * \n * @returns The next available cluster, or undefined if all clusters are in use\n */\n public getNextCluster(): BridgeClientCluster | undefined {\n for (const cluster of this.clusterList) {\n if (!cluster.isUsed()) {\n return cluster;\n }\n }\n return undefined; // No available clusters\n }\n\n /**\n * Retrieves multiple available clusters up to the specified count.\n * Each returned cluster is marked as used.\n * \n * @param count - The maximum number of clusters to retrieve\n * @returns An array of available clusters (may be fewer than requested if not enough are available)\n */\n public getNextClusters(count: number): BridgeClientCluster[] {\n const availableClusters: BridgeClientCluster[] = [];\n for (const cluster of this.clusterList) {\n if (!cluster.isUsed() && availableClusters.length < count) {\n availableClusters.push(cluster);\n }\n }\n return availableClusters; // Returns the clusters that were found\n }\n\n /**\n * Sets the used status of a specific cluster by its ID.\n *\n * @param clusterID - The ID of the cluster to update\n * @param connection - The connection to associate with the cluster\n */\n public clearClusterConnection(clusterID: number): void {\n const cluster = this.clusterList.find(c => c.clusterID === clusterID);\n if (cluster) {\n cluster.setConnection(undefined);\n }\n }\n\n public getClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[] {\n return this.clusterList.filter(cluster =>\n cluster.connection?.instanceID === connection.instanceID\n );\n }\n\n public getOldClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[] {\n return this.clusterList.filter(cluster =>\n cluster.oldConnection?.instanceID === connection.instanceID\n );\n }\n\n public checkAllClustersConnected(): boolean {\n for (const cluster of this.clusterList) {\n if (cluster.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED){\n return false; // At least one cluster is not in use\n }\n }\n return true; // All clusters are in use\n }\n\n\n findMostAndLeastClustersForConnections(\n connectedClients: BridgeClientConnection[]\n ): {\n most: BridgeClientConnection | undefined,\n least: BridgeClientConnection | undefined\n } {\n\n const openClients = connectedClients.filter(x => !x.dev)\n\n const devClients = connectedClients.filter(x => x.dev)\n const summDevConnectedClusters = devClients.map(c => this.getClusterForConnection(c).length).reduce((a, b) => a + b, 0);\n\n let most: BridgeClientConnection | undefined;\n let least: BridgeClientConnection | undefined;\n let remainder = ((this.clusterToStart - summDevConnectedClusters) % openClients.length || 0);\n\n for (const client of openClients) {\n const clusters = this.getClusterForConnection(client);\n\n if (!most || clusters.length > this.getClusterForConnection(most).length) {\n most = client;\n }\n\n if (!least || clusters.length < this.getClusterForConnection(least).length) {\n least = client;\n }\n }\n\n if (most && least) {\n const mostCount = this.getClusterForConnection(most).length;\n const leastCount = this.getClusterForConnection(least).length;\n\n // Only recluster if the difference is greater than remainder\n if (mostCount - leastCount <= remainder) {\n return {most: undefined, least: undefined};\n }\n }\n\n return {most, least};\n }\n\n getClusterWithLowestLoad(connectedClients: Map): BridgeClientConnection | undefined {\n let lowestLoadClient: BridgeClientConnection | undefined;\n let lowestLoad = Infinity;\n\n for (const client of connectedClients.values().filter(c =>\n c.connectionStatus === BridgeClientConnectionStatus.READY && !c.dev)) {\n const clusters = this.getClusterForConnection(client);\n\n const load = clusters.length; // Assuming load is determined by the number of clusters assigned\n if (load < lowestLoad) {\n lowestLoad = load;\n lowestLoadClient = client;\n }\n }\n\n return lowestLoadClient; // Returns the client with the lowest load, or undefined if no clients are connected\n }\n\n getClusterOfShard(shardID: number) {\n return this.clusterList.find(c => c.shardList.includes(shardID));\n }\n}\n","export class ShardingUtil {\n public static getShardIDForGuild(guildID: string, totalShards: number): number {\n if (!guildID || totalShards <= 0) {\n throw new Error(\"Invalid guild ID or total shards\");\n }\n\n return Number(BigInt(guildID) >> 22n) % totalShards;\n }\n}","import {Client, GatewayIntentsString, Status} from \"discord.js\";\nimport {EventManager} from \"../general/EventManager\";\nimport os from \"os\";\nexport class Cluster {\n\n public readonly instanceID: number;\n\n public readonly clusterID: number;\n\n public readonly shardList: number[] = [];\n\n public readonly totalShards: number;\n\n public readonly token: string;\n\n public readonly intents: GatewayIntentsString[];\n\n public eventManager: EventManager;\n\n public client!: T;\n\n public onSelfDestruct?: () => void;\n\n private readonly eventMap: {\n 'message': ((message: unknown) => void) | undefined,\n 'request': ((message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined,\n 'CLUSTER_READY': (() => void) | undefined,\n } = {\n message: undefined, request: undefined, CLUSTER_READY: undefined,\n }\n\n constructor(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]) {\n this.instanceID = instanceID;\n this.clusterID = clusterID;\n this.shardList = shardList;\n this.totalShards = totalShards;\n this.token = token;\n this.intents = intents;\n this.eventManager = new EventManager((message: unknown) => {\n return new Promise((resolve, reject) => {\n if (typeof process.send !== 'function') {\n reject(new Error(\"Process does not support sending messages\"));\n return;\n }\n\n process.send?.(message, undefined, undefined, (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }, (message: unknown) => {\n this._onMessage(message);\n }, (message: unknown) => {\n return this._onRequest(message);\n });\n process.on(\"message\", (message) => {\n this.eventManager.receive(message);\n })\n }\n\n static initial(): Cluster {\n const args = process.env;\n\n if (args.SHARD_LIST == undefined || args.INSTANCE_ID == undefined || args.TOTAL_SHARDS == undefined || args.TOKEN == undefined || args.INTENTS == undefined || args.CLUSTER_ID == undefined) {\n throw new Error(\"Missing required environment variables\");\n }\n\n const shardList = args.SHARD_LIST.split(',').map(Number);\n\n const totalShards = Number(args.TOTAL_SHARDS);\n\n const instanceID = Number(args.INSTANCE_ID);\n const clusterID = Number(args.CLUSTER_ID);\n\n const token = args.TOKEN;\n\n const intents = args.INTENTS.split(',').map(i => i.trim()) as GatewayIntentsString[];\n\n return new Cluster(instanceID, clusterID, shardList, totalShards, token, intents);\n }\n\n triggerReady(guilds: number, members: number) {\n this.eventManager.send({\n type: 'CLUSTER_READY',\n id: this.clusterID,\n guilds: guilds,\n members: members,\n });\n\n if(this.eventMap?.CLUSTER_READY) {\n this.eventMap?.CLUSTER_READY();\n }\n }\n\n triggerError(e: any) {\n this.eventManager.send({\n type: 'CLUSTER_ERROR',\n id: this.clusterID,\n });\n }\n\n private async wait(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n private _onMessage(message: unknown): void {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CUSTOM' && this.eventMap.message) {\n this.eventMap.message!(m.data);\n }\n }\n\n private _onRequest(message: unknown): unknown {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(m.data, resolve, reject);\n });\n } else if(m.type == 'CLUSTER_HEARTBEAT'){\n const startTime = process.hrtime.bigint();\n const startUsage = process.cpuUsage();\n\n (async () => {\n await this.wait(500);\n })();\n\n const endTime = process.hrtime.bigint();\n const usageDiff = process.cpuUsage(startUsage);\n\n const elapsedTimeUs = Number((endTime - startTime) / 1000n);\n const totalCPUTime = usageDiff.user + usageDiff.system;\n\n const cpuCount = os.cpus().length;\n const cpuPercent = (totalCPUTime / (elapsedTimeUs * cpuCount)) * 100;\n\n // Collect per-shard ping information in addition to the overall ws ping\n let shardPings: { id: number, ping: number, status: Status, uptime?: unknown, guilds: number, members: number }[] = [];\n try {\n const shards = this.client.ws.shards;\n\n if(shards) {\n shards.forEach((shard) => {\n shardPings.push({ id: shard.id, ping: shard.ping, status: shard.status,\n guilds: this.client.guilds.cache.filter(g => g.shardId === shard.id).size,\n members: this.client.guilds.cache.filter(g => g.shardId === shard.id).reduce((acc, g) => acc + g.memberCount, 0)\n });\n\n this.client.shard?.fetchClientValues('uptime', shard.id).then(values => {\n shardPings[shard.id][\"uptime\"] = values\n console.log(values)\n }).catch(e => {\n\n })\n })\n }\n } catch (_) {\n // ignore and keep empty shardPings on failure\n }\n\n return {\n cpu: { raw: process.cpuUsage(), cpuPercent: cpuPercent.toFixed(2) },\n memory: { raw: process.memoryUsage(),\n memoryPercent: ((process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100).toFixed(2) + '%',\n usage: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + 'MB'\n },\n ping: this.client.ws.ping,\n shardPings: shardPings,\n }\n } else if(m.type == 'BROADCAST_EVAL'){\n const broadcast = message as { type: 'BROADCAST_EVAL', data: string }\n\n const fn = eval(`(${broadcast.data})`);\n\n const result = fn(this.client);\n if(result instanceof Promise){\n return new Promise((resolve, reject) => {\n result.then(res => {\n resolve(res);\n }).catch(err => {\n reject(err);\n });\n });\n } else {\n return result;\n }\n } else if(m.type == 'SELF_DESTRUCT') {\n if(this.onSelfDestruct) {\n this.onSelfDestruct();\n }\n }\n return undefined;\n }\n\n public on(event: K, listener: ClusterEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public sendMessage(data: unknown) {\n this.eventManager.send({\n type: 'CUSTOM',\n data: data,\n });\n }\n\n public sendRequest(data: unknown, timeout = 5000): Promise {\n return this.eventManager.request({\n type: 'CUSTOM',\n data: data,\n }, timeout);\n }\n\n public broadcastEval(fn: (cluster: T) => Result, timeout = 20000): Promise {\n return this.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: fn.toString(),\n }, timeout);\n }\n\n\n public sendMessageToClusterOfGuild(guildID: string, message: unknown): void {\n if (this.eventManager) {\n this.eventManager.send({\n type: 'REDIRECT_MESSAGE_TO_GUILD',\n guildID: guildID,\n data: message\n });\n }\n }\n\n public sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n if (this.eventManager) {\n this.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n guildID: guildID,\n data: message\n }, timeout).then((response) => {\n resolve(response);\n }).catch((error) => {\n reject(error);\n });\n } else {\n reject(new Error(\"Event manager is not initialized\"));\n }\n });\n }\n}\n\nexport type ClusterEventListeners = {\n message: (message: unknown) => void;\n request: (message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void;\n\n CLUSTER_READY: () => void;\n};","import {ChildProcess} from \"child_process\";\nimport {EventManager} from \"../general/EventManager\";\n\nexport type ClusterProcessState = 'starting' | 'running' | 'stopped';\n\nexport class ClusterProcess {\n public readonly child: ChildProcess;\n public readonly eventManager: EventManager;\n public readonly id: number;\n public readonly shardList: number[];\n public readonly totalShards: number;\n public status: ClusterProcessState;\n public readonly createdAt: number = Date.now();\n\n private _onMessage?: (message: unknown) => void;\n private _onRequest?: (message: unknown) => unknown;\n\n constructor(id: number, child: ChildProcess, shardList: number[], totalShards: number) {\n this.id = id;\n this.child = child;\n this.shardList = shardList;\n this.totalShards = totalShards;\n this.status = 'starting';\n this.eventManager = new EventManager((message) => {\n return new Promise((resolve, reject) => {\n this.child.send(message, (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n })\n }, (message) => {\n if (this._onMessage) {\n this._onMessage(message);\n }\n }, (message) => {\n if (this._onRequest) {\n return this._onRequest(message);\n }\n return undefined;\n })\n\n this.child.on('message', (message) => {\n this.eventManager.receive(message);\n });\n\n // Ensure we do not retain pending requests if the child dies or errors\n this.child.on('exit', () => {\n this.eventManager.close('child process exited');\n });\n this.child.on('error', () => {\n this.eventManager.close('child process error');\n });\n }\n\n onMessage(callback: (message: unknown) => void) {\n this._onMessage = callback;\n }\n\n onRequest(callback: (message: unknown) => unknown) {\n this._onRequest = callback;\n }\n\n public sendMessage(data: unknown) {\n this.eventManager.send({\n type: 'CUSTOM',\n data: data,\n });\n }\n\n public sendRequest(data: unknown, timeout = 5000): Promise {\n return this.eventManager.request({\n type: 'CUSTOM',\n data: data,\n }, timeout);\n }\n}","import {fork} from 'child_process';\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport abstract class BotInstance {\n\n private readonly entryPoint: string;\n\n private readonly execArgv: string[];\n\n public readonly clients: Map = new Map();\n\n protected constructor(entryPoint: string, execArgv?: string[]) {\n this.entryPoint = entryPoint;\n this.execArgv = execArgv ?? [];\n }\n\n protected readonly eventMap: BotInstanceEventListeners = {\n 'message': undefined,\n 'request': undefined,\n\n 'PROCESS_KILLED': undefined,\n 'PROCESS_SELF_DESTRUCT_ERROR': undefined,\n 'PROCESS_SPAWNED': undefined,\n 'ERROR': undefined,\n 'PROCESS_ERROR': undefined,\n 'CLUSTER_READY': undefined,\n 'CLUSTER_ERROR': undefined,\n 'CLUSTER_RECLUSTER': undefined,\n 'BRIDGE_CONNECTION_ESTABLISHED': undefined,\n 'BRIDGE_CONNECTION_CLOSED': undefined,\n 'BRIDGE_CONNECTION_STATUS_CHANGE': undefined,\n 'INSTANCE_STOP': undefined,\n 'INSTANCE_STOPPED': undefined,\n 'SELF_CHECK_SUCCESS': undefined,\n 'SELF_CHECK_ERROR': undefined,\n 'SELF_CHECK_RECEIVED': undefined,\n }\n\n protected startProcess(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]): void {\n try {\n const child = fork(this.entryPoint, {\n env: {\n INSTANCE_ID: instanceID.toString(),\n CLUSTER_ID: clusterID.toString(),\n SHARD_LIST: shardList.join(','),\n TOTAL_SHARDS: totalShards.toString(),\n TOKEN: token,\n INTENTS: intents.join(','),\n FORCE_COLOR: 'true'\n },\n stdio: 'inherit',\n execArgv: this.execArgv,\n silent: false,\n detached: true,\n })\n\n const client = new ClusterProcess(clusterID, child, shardList, totalShards);\n\n child.stdout?.on('data', (data) => {\n process.stdout.write(data);\n });\n\n child.stderr?.on('data', (data) => {\n process.stderr.write(data);\n });\n\n child.on(\"spawn\", () => {\n if(this.eventMap.PROCESS_SPAWNED) this.eventMap.PROCESS_SPAWNED(client);\n\n this.setClusterSpawned(client);\n\n this.clients.set(clusterID, client);\n\n client.onMessage((message) => {\n this.onMessage(client, message);\n })\n\n client.onRequest((message) => {\n return this.onRequest(client, message);\n });\n });\n\n child.on(\"error\", (err) => {\n if(this.eventMap.PROCESS_ERROR) this.eventMap.PROCESS_ERROR(client, err);\n })\n\n child.on(\"exit\", (err: Error) => {\n if(client.status !== 'stopped') {\n client.status = 'stopped';\n this.killProcess(client, `Process exited: ${err?.message}`);\n }\n })\n } catch (error) {\n throw new Error(`Failed to start process for cluster ${clusterID}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n protected killProcess(client: ClusterProcess, reason: string): void {\n client.status = 'stopped';\n\n client.eventManager.request({\n type: 'SELF_DESTRUCT',\n reason: reason\n }, 5000).catch(() => {\n if(this.eventMap.PROCESS_SELF_DESTRUCT_ERROR) this.eventMap.PROCESS_SELF_DESTRUCT_ERROR(client, reason, 'Cluster didnt respond to shot-call.');\n }).finally(() => {\n if (client.child && client.child.pid) {\n if(client.child.kill(\"SIGKILL\")) {\n if(this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, true);\n } else {\n if(this.eventMap.ERROR) this.eventMap.ERROR(`Failed to kill process for cluster ${client.id}`);\n client.child.kill(\"SIGKILL\");\n }\n try { process.kill(-client.child.pid) } catch {}\n } else {\n if(this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, false);\n }\n this.clients.delete(client.id);\n this.setClusterStopped(client, reason);\n })\n }\n\n protected abstract setClusterStopped(client: ClusterProcess, reason: string): void;\n\n protected abstract setClusterReady(client: ClusterProcess, guilds: number, members: number): void;\n\n protected abstract setClusterSpawned(client: ClusterProcess): void;\n\n public abstract start(): void;\n\n private onMessage(client: ClusterProcess, message: any): void {\n if(message.type === 'CLUSTER_READY') {\n client.status = 'running';\n if(this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(client);\n this.setClusterReady(client, message.guilds || 0, message.members || 0);\n }\n\n if (message.type === 'CLUSTER_ERROR') {\n client.status = 'stopped';\n if(this.eventMap.CLUSTER_ERROR) this.eventMap.CLUSTER_ERROR(client, message.error);\n this.killProcess(client, 'Cluster error: ' + message.error);\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.message) {\n this.eventMap.message!(client, message.data);\n }\n }\n\n protected abstract onRequest(client: ClusterProcess, message: any): Promise;\n\n public on(event: K, listener: BotInstanceEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n for (const client of this.clients.values()) {\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if (client.shardList.includes(shardID)) {\n client.eventManager.request({\n type: 'CUSTOM',\n data: message\n }, timeout).then(resolve).catch(reject);\n return;\n }\n }\n reject(new Error(`No cluster found for guild ${guildID}`));\n });\n }\n\n public sendRequestToCluster(cluster: ClusterProcess, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n cluster.eventManager.request({\n type: 'CUSTOM',\n data: message\n }, timeout).then(resolve).catch(reject);\n return;\n });\n }\n}\n\nexport type BotInstanceEventListeners = {\n 'message': ((client: ClusterProcess,message: unknown) => void) | undefined,\n 'request': ((client: ClusterProcess, message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined,\n\n 'PROCESS_KILLED': ((client: ClusterProcess, reason: string, processKilled: boolean) => void) | undefined,\n 'PROCESS_SELF_DESTRUCT_ERROR': ((client: ClusterProcess, reason: string, error: unknown) => void) | undefined,\n 'PROCESS_SPAWNED': ((client: ClusterProcess) => void) | undefined,\n 'PROCESS_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined,\n 'CLUSTER_READY': ((client: ClusterProcess) => void) | undefined,\n 'CLUSTER_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined,\n 'CLUSTER_RECLUSTER': ((client: ClusterProcess) => void) | undefined,\n 'ERROR': ((error: string) => void) | undefined,\n\n 'BRIDGE_CONNECTION_ESTABLISHED': (() => void) | undefined,\n 'BRIDGE_CONNECTION_CLOSED': ((reason: string) => void) | undefined,\n 'BRIDGE_CONNECTION_STATUS_CHANGE': ((status: number) => void) | undefined,\n 'INSTANCE_STOP': (() => void) | undefined,\n 'INSTANCE_STOPPED': (() => void) | undefined,\n\n 'SELF_CHECK_SUCCESS': (() => void) | undefined,\n 'SELF_CHECK_ERROR': ((error: string) => void) | undefined,\n 'SELF_CHECK_RECEIVED': ((data: { clusterList: number[] }) => void) | undefined,\n};","import {BotInstance} from \"./BotInstance\";\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {Client} from \"net-ipc\";\nimport {EventManager} from \"../general/EventManager\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport enum BridgeConnectionStatus {\n CONNECTED,\n DISCONNECTED,\n}\n\nexport class ManagedInstance extends BotInstance {\n\n private readonly host: string;\n\n private readonly port: number;\n\n private readonly instanceID: number;\n\n private eventManager!: EventManager;\n\n private connectionStatus: BridgeConnectionStatus = BridgeConnectionStatus.DISCONNECTED;\n\n private data: unknown;\n\n private dev: boolean = false;\n\n constructor(entryPoint: string, host: string, port: number, instanceID: number, data: unknown, execArgv?: string[], dev?: boolean) {\n super(entryPoint, execArgv);\n\n this.host = host;\n this.port = port;\n this.instanceID = instanceID;\n this.data = data;\n this.dev = dev || false;\n }\n\n public start() {\n const client = new Client({\n host: this.host,\n port: this.port,\n reconnect: true,\n retries: 100\n });\n\n this.eventManager = new EventManager((message) => {\n if(client.status == 3) {\n return client.send(message);\n }\n return Promise.reject(new Error('Client is not ready to send messages'));\n }, (message) => {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CLUSTER_CREATE') {\n this.onClusterCreate(m.data)\n } else if (m.type == 'CLUSTER_STOP') {\n this.onClusterStop(m.data);\n } else if(m.type == 'CLUSTER_RECLUSTER') {\n this.onClusterRecluster(m.data);\n } else if(m.type == 'INSTANCE_STOP') {\n if(this.eventMap.INSTANCE_STOP) {\n this.eventMap.INSTANCE_STOP();\n }\n } else if(m.type == 'INSTANCE_STOPPED') {\n if(this.eventMap.INSTANCE_STOPPED) {\n this.eventMap.INSTANCE_STOPPED();\n }\n }\n }, (message) => {\n return this.onBridgeRequest(message);\n });\n\n setInterval(() => {\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.selfCheck();\n }\n }, 2500); // 5 minutes\n\n client.connect({\n id: this.instanceID,\n dev: this.dev,\n data: this.data,\n }).then(_ => {\n if(this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED();\n this.connectionStatus = BridgeConnectionStatus.CONNECTED;\n\n client.on(\"message\", (message) => {\n this.eventManager?.receive(message);\n })\n client.on(\"close\", (reason) => {\n if(this.eventMap.BRIDGE_CONNECTION_CLOSED) this.eventMap.BRIDGE_CONNECTION_CLOSED(reason);\n\n // kill all\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.clients.forEach((client) => {\n this.killProcess(client, 'Bridge connection closed');\n });\n }\n this.connectionStatus = BridgeConnectionStatus.DISCONNECTED;\n });\n\n client.on(\"status\", (status) => {\n if(this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE) this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE(status);\n\n if(status == 4){\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.clients.forEach((client) => {\n this.killProcess(client, 'Bridge connection closed');\n });\n }\n this.connectionStatus = BridgeConnectionStatus.DISCONNECTED;\n } else if(status == 3){\n this.connectionStatus = BridgeConnectionStatus.CONNECTED;\n if(this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED();\n }\n });\n })\n }\n\n private selfCheck() {\n this.eventManager.request({\n type: 'SELF_CHECK'\n }, 1000 * 60).then((r) => {\n const response = r as { clusterList: number[] };\n\n if(this.eventMap.SELF_CHECK_RECEIVED) {\n this.eventMap.SELF_CHECK_RECEIVED(response);\n }\n\n const startingClusters = this.clients.values().filter(c => c.status == 'starting').toArray();\n startingClusters.forEach((c: ClusterProcess) => {\n if (Date.now() - c.createdAt > 10 * 60 * 1000) {\n this.killProcess(c, 'Cluster took too long to start');\n }\n })\n\n // check if there is an wrong cluster on this host\n const wrongClusters = this.clients.values().filter(c => !response.clusterList.includes(c.id)).toArray();\n if(wrongClusters.length > 0) {\n if(this.eventMap.SELF_CHECK_ERROR) {\n this.eventMap.SELF_CHECK_ERROR(`Self check found wrong clusters: ${wrongClusters.map(c => c.id).join(', ')}`);\n }\n wrongClusters.forEach(c => {\n this.killProcess(c, 'Self check found wrong cluster');\n });\n } else {\n if(this.eventMap.SELF_CHECK_SUCCESS) {\n this.eventMap.SELF_CHECK_SUCCESS();\n }\n }\n }).catch((err) => {\n if(this.eventMap.SELF_CHECK_ERROR) {\n this.eventMap.SELF_CHECK_ERROR(`Self check failed: ${err}`);\n }\n });\n }\n\n protected setClusterStopped(client: ClusterProcess, reason: string): void {\n this.eventManager?.send({\n type: 'CLUSTER_STOPPED',\n data: {\n id: client.id,\n reason: reason\n }\n }).catch(() => {\n return null\n });\n }\n\n protected setClusterReady(client: ClusterProcess, guilds: number, members: number): void {\n this.eventManager?.send({\n type: 'CLUSTER_READY',\n data: {\n id: client.id,\n guilds: guilds,\n members: members\n }\n });\n }\n\n protected setClusterSpawned(client: ClusterProcess): void {\n this.eventManager?.send({\n type: 'CLUSTER_SPAWNED',\n data: {\n id: client.id\n }\n });\n }\n\n private onClusterCreate(message: unknown){\n const m = message as { clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[] }\n\n if(this.clients.has(m.clusterID)) {\n this.eventManager?.send({\n type: 'CLUSTER_STOPPED',\n data: {\n id: m.clusterID,\n reason: 'Cluster already exists'\n }\n }).catch(() => {\n return null\n });\n return;\n }\n\n this.startProcess(this.instanceID, m.clusterID, m.shardList, m.totalShards, m.token, m.intents);\n }\n\n private onClusterStop(message: unknown) {\n const m = message as { id: number };\n const cluster = this.clients.get(m.id)\n if (cluster) {\n this.killProcess(cluster, `Request to stop cluster ${m.id}`);\n }\n }\n\n private onClusterRecluster(message: unknown) {\n const m = message as { clusterID: number }\n const cluster = this.clients.get(m.clusterID);\n if (this.eventMap.CLUSTER_RECLUSTER && cluster) {\n this.eventMap.CLUSTER_RECLUSTER(cluster);\n }\n }\n\n protected onRequest(client: ClusterProcess, message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = message.guildID;\n const data = message.data;\n\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if(client.shardList.includes(shardID)) {\n return client.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return this.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n guildID: guildID,\n data: data\n }, 5000)\n }\n }\n\n if(message.type == 'BROADCAST_EVAL') {\n return this.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data\n }, 5000)\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(client, message.data, resolve, reject);\n });\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n private onBridgeRequest(message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const clusterID = message.clusterID;\n const data = message.data;\n\n const cluster = this.clients.get(clusterID);\n if(cluster){\n return cluster.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`));\n }\n } else if(message.type == 'CLUSTER_HEARTBEAT'){\n const clusterID = message.data.clusterID;\n const cluster = this.clients.get(clusterID);\n if (cluster) {\n return new Promise((resolve, reject) => {\n cluster.eventManager.request({\n type: 'CLUSTER_HEARTBEAT'\n }, 15000).then((r) => {\n resolve(r);\n }).catch((err) => {\n reject(err);\n });\n })\n } else {\n return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`));\n }\n } else if(message.type == 'BROADCAST_EVAL') {\n return Promise.all(this.clients.values().filter(c => c.status == 'running').map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data,\n }, 5000);\n }));\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n stopInstance(): void {\n this.eventManager?.send({\n type: 'INSTANCE_STOP'\n });\n }\n\n}","import {BotInstance} from \"./BotInstance\";\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport class StandaloneInstance extends BotInstance {\n private readonly totalClusters: number;\n private readonly shardsPerCluster: number;\n\n public readonly token: string;\n public readonly intents: GatewayIntentsString[];\n\n constructor(entryPoint: string, shardsPerCluster: number, totalClusters: number, token: string, intents: GatewayIntentsString[], execArgv?: string[]) {\n super(entryPoint, execArgv);\n this.shardsPerCluster = shardsPerCluster;\n this.totalClusters = totalClusters;\n this.token = token;\n this.intents = intents;\n }\n\n get totalShards(): number {\n return this.shardsPerCluster * this.totalClusters;\n }\n\n private calculateClusters(): Record {\n const clusters: Record = {};\n for (let i = 0; i < this.totalClusters; i++) {\n clusters[i] = [];\n for (let j = 0; j < this.shardsPerCluster; j++) {\n clusters[i].push(i * this.shardsPerCluster + j);\n }\n }\n return clusters;\n }\n\n public start(): void {\n const clusters = this.calculateClusters();\n for (const [id, shardList] of Object.entries(clusters)) {\n this.startProcess(1, Number(id), shardList, this.totalShards, this.token, this.intents);\n }\n }\n\n protected setClusterStopped(client: ClusterProcess, reason: string): void {\n this.clients.delete(client.id);\n this.restartProcess(client);\n }\n\n protected setClusterReady(client: ClusterProcess): void {\n \n }\n\n protected setClusterSpawned(client: ClusterProcess): void {\n\n }\n\n private restartProcess(client: ClusterProcess): void {\n this.startProcess(1, client.id, client.shardList, this.totalShards, this.token, this.intents);\n }\n\n protected onRequest(client: ClusterProcess, message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = message.guildID;\n const data = message.data;\n\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if(client.shardList.includes(shardID)) {\n return client.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return Promise.reject(new Error(`Shard ID ${shardID} not found in cluster ${client.id} for guild ${guildID}`));\n }\n }\n\n if(message.type == 'BROADCAST_EVAL') {\n return Promise.all(\n this.clients.values().map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data,\n }, 5000);\n })\n );\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(client, message.data, resolve, reject);\n });\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n}"],"mappings":"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","names":["BridgeClientClusterConnectionStatus","result","BridgeClientConnectionStatus","message","m","message","message","m","os","fn","message","message","import_net_ipc","BridgeConnectionStatus","message","m","client","message"]} \ No newline at end of file diff --git a/dist/index.mjs b/dist/index.mjs new file mode 100644 index 0000000..bdf0a01 --- /dev/null +++ b/dist/index.mjs @@ -0,0 +1,1526 @@ +// src/bridge/BridgeClientCluster.ts +var BridgeClientClusterConnectionStatus = /* @__PURE__ */ ((BridgeClientClusterConnectionStatus2) => { + BridgeClientClusterConnectionStatus2["REQUESTING"] = "requesting"; + BridgeClientClusterConnectionStatus2["STARTING"] = "starting"; + BridgeClientClusterConnectionStatus2["CONNECTED"] = "connected"; + BridgeClientClusterConnectionStatus2["RECLUSTERING"] = "reclustering"; + BridgeClientClusterConnectionStatus2["DISCONNECTED"] = "disconnected"; + return BridgeClientClusterConnectionStatus2; +})(BridgeClientClusterConnectionStatus || {}); +var BridgeClientCluster = class { + clusterID; + shardList; + connectionStatus = "disconnected" /* DISCONNECTED */; + connection; + oldConnection; + missedHeartbeats = 0; + heartbeatResponse; + heartbeatPending = false; + startedAt; + constructor(clusterID, shardList) { + this.clusterID = clusterID; + this.shardList = shardList; + } + setConnection(connection) { + if (connection == void 0) { + this.connectionStatus = "disconnected" /* DISCONNECTED */; + this.connection = void 0; + return; + } + if (this.connection) { + throw new Error(`Connection already set for cluster ${this.clusterID}`); + } + this.connectionStatus = "requesting" /* REQUESTING */; + this.connection = connection; + } + setOldConnection(connection) { + this.oldConnection = connection; + } + isUsed() { + return this.connection != void 0 && this.connectionStatus !== "disconnected" /* DISCONNECTED */; + } + reclustering(connection) { + this.connectionStatus = "reclustering" /* RECLUSTERING */; + this.oldConnection = this.connection; + this.connection = connection; + } + addMissedHeartbeat() { + this.missedHeartbeats++; + } + removeMissedHeartbeat() { + if (this.missedHeartbeats > 0) { + this.missedHeartbeats--; + } + } + resetMissedHeartbeats() { + this.missedHeartbeats = 0; + } +}; + +// src/general/EventManager.ts +var EventManager = class { + pendingPayloads = /* @__PURE__ */ new Map(); + // Track per-request timeout handles so we can clear them on resolve/reject + pendingTimeouts = /* @__PURE__ */ new Map(); + _send; + _on; + _request; + constructor(send, on, request) { + this._send = send; + this._on = on; + this._request = request; + } + async send(data) { + return this._send({ + id: crypto.randomUUID(), + type: "message", + data + }); + } + async request(payload, timeout) { + const id = crypto.randomUUID(); + return new Promise((resolve, reject) => { + this._send({ + id, + type: "request", + data: payload + }); + this.pendingPayloads.set(id, { + resolve, + reject + }); + const t = setTimeout(() => { + if (this.pendingPayloads.has(id)) { + this.pendingPayloads.delete(id); + this.pendingTimeouts.delete(id); + reject({ + error: `Request with id ${id} timed out` + }); + } + }, timeout); + this.pendingTimeouts.set(id, t); + }); + } + receive(possiblePayload) { + if (typeof possiblePayload !== "object" || possiblePayload === null) { + return; + } + const payload = possiblePayload; + if (!payload.id || !payload.type) { + return; + } + if (payload.type === "message") { + this._on(payload.data); + return; + } + if (payload.type === "response") { + const resolve = this.pendingPayloads.get(payload.id)?.resolve; + if (resolve) { + resolve(payload.data); + this.pendingPayloads.delete(payload.id); + const to = this.pendingTimeouts.get(payload.id); + if (to) clearTimeout(to); + this.pendingTimeouts.delete(payload.id); + } + return; + } + if (payload.type === "response_error") { + const reject = this.pendingPayloads.get(payload.id)?.reject; + if (reject) { + reject(payload.data); + this.pendingPayloads.delete(payload.id); + const to = this.pendingTimeouts.get(payload.id); + if (to) clearTimeout(to); + this.pendingTimeouts.delete(payload.id); + } + return; + } + if (payload.type === "request") { + const data = this._request(payload.data); + if (data instanceof Promise) { + data.then((result2) => { + this._send({ + id: payload.id, + type: "response", + data: result2 + }); + }).catch((error) => { + this._send({ + id: payload.id, + type: "response_error", + data: error + }); + }); + } else { + this._send({ + id: payload.id, + type: "response", + data + }); + } + return; + } + } + // Reject and clear all pending requests to avoid memory leaks when a connection/process closes + close(reason) { + if (this.pendingPayloads.size === 0 && this.pendingTimeouts.size === 0) return; + const err = { error: reason || "EventManager closed" }; + for (const [id, handlers] of this.pendingPayloads.entries()) { + try { + handlers.reject(err); + } catch (_) { + } + this.pendingPayloads.delete(id); + const to = this.pendingTimeouts.get(id); + if (to) clearTimeout(to); + this.pendingTimeouts.delete(id); + } + for (const to of this.pendingTimeouts.values()) { + clearTimeout(to); + } + this.pendingTimeouts.clear(); + } +}; + +// src/bridge/BridgeClientConnection.ts +var BridgeClientConnectionStatus = /* @__PURE__ */ ((BridgeClientConnectionStatus2) => { + BridgeClientConnectionStatus2["READY"] = "ready"; + BridgeClientConnectionStatus2["PENDING_STOP"] = "pending_stop"; + return BridgeClientConnectionStatus2; +})(BridgeClientConnectionStatus || {}); +var BridgeClientConnection = class { + instanceID; + eventManager; + connection; + data; + connectionStatus = "ready" /* READY */; + dev = false; + establishedAt = Date.now(); + _onMessage; + _onRequest; + constructor(instanceID, connection, data, dev) { + this.instanceID = instanceID; + this.connection = connection; + this.data = data; + this.dev = dev || false; + this.eventManager = new EventManager((message2) => { + if (!this.connection?.connection?.closed) { + return this.connection.send(message2); + } + return Promise.reject(new Error("Connection is closed, cannot send message")); + }, (message2) => { + if (this._onMessage) { + this._onMessage(message2); + } + }, (message2) => { + if (this._onRequest) { + return this._onRequest(message2); + } + return void 0; + }); + } + messageReceive(message2) { + this.eventManager.receive(message2); + } + onRequest(callback) { + this._onRequest = callback; + } + onMessage(callback) { + this._onMessage = callback; + } +}; + +// src/bridge/Bridge.ts +import { Server } from "net-ipc"; + +// src/bridge/ClusterCalculator.ts +var ClusterCalculator = class { + /** The total number of clusters to initialize */ + clusterToStart; + /** The number of shards that each cluster will manage */ + shardsPerCluster; + /** List of all clusters managed by this calculator */ + clusterList = []; + /** + * Creates a new ClusterCalculator and initializes the clusters. + * + * @param clusterToStart - The number of clusters to create + * @param shardsPerCluster - The number of shards each cluster will manage + */ + constructor(clusterToStart, shardsPerCluster) { + this.shardsPerCluster = shardsPerCluster; + this.clusterToStart = clusterToStart; + this.calculateClusters(); + } + /** + * Calculates and initializes all clusters with their assigned shards. + * Each cluster is assigned a sequential range of shard IDs based on its cluster index. + */ + calculateClusters() { + const clusters = /* @__PURE__ */ new Map(); + for (let i = 0; i < this.clusterToStart; i++) { + clusters.set(i, []); + for (let j = 0; j < this.shardsPerCluster; j++) { + clusters.get(i)?.push(i * this.shardsPerCluster + j); + } + } + for (let [clusterIndex, clusterShards] of clusters.entries()) { + this.clusterList.push(new BridgeClientCluster(clusterIndex, clusterShards)); + } + } + /** + * Retrieves the next available (unused) cluster and marks it as used. + * + * @returns The next available cluster, or undefined if all clusters are in use + */ + getNextCluster() { + for (const cluster of this.clusterList) { + if (!cluster.isUsed()) { + return cluster; + } + } + return void 0; + } + /** + * Retrieves multiple available clusters up to the specified count. + * Each returned cluster is marked as used. + * + * @param count - The maximum number of clusters to retrieve + * @returns An array of available clusters (may be fewer than requested if not enough are available) + */ + getNextClusters(count) { + const availableClusters = []; + for (const cluster of this.clusterList) { + if (!cluster.isUsed() && availableClusters.length < count) { + availableClusters.push(cluster); + } + } + return availableClusters; + } + /** + * Sets the used status of a specific cluster by its ID. + * + * @param clusterID - The ID of the cluster to update + * @param connection - The connection to associate with the cluster + */ + clearClusterConnection(clusterID) { + const cluster = this.clusterList.find((c) => c.clusterID === clusterID); + if (cluster) { + cluster.setConnection(void 0); + } + } + getClusterForConnection(connection) { + return this.clusterList.filter( + (cluster) => cluster.connection?.instanceID === connection.instanceID + ); + } + getOldClusterForConnection(connection) { + return this.clusterList.filter( + (cluster) => cluster.oldConnection?.instanceID === connection.instanceID + ); + } + checkAllClustersConnected() { + for (const cluster of this.clusterList) { + if (cluster.connectionStatus != "connected" /* CONNECTED */) { + return false; + } + } + return true; + } + findMostAndLeastClustersForConnections(connectedClients) { + const openClients = connectedClients.filter((x) => !x.dev); + const devClients = connectedClients.filter((x) => x.dev); + const summDevConnectedClusters = devClients.map((c) => this.getClusterForConnection(c).length).reduce((a, b) => a + b, 0); + let most; + let least; + let remainder = (this.clusterToStart - summDevConnectedClusters) % openClients.length || 0; + for (const client of openClients) { + const clusters = this.getClusterForConnection(client); + if (!most || clusters.length > this.getClusterForConnection(most).length) { + most = client; + } + if (!least || clusters.length < this.getClusterForConnection(least).length) { + least = client; + } + } + if (most && least) { + const mostCount = this.getClusterForConnection(most).length; + const leastCount = this.getClusterForConnection(least).length; + if (mostCount - leastCount <= remainder) { + return { most: void 0, least: void 0 }; + } + } + return { most, least }; + } + getClusterWithLowestLoad(connectedClients) { + let lowestLoadClient; + let lowestLoad = Infinity; + for (const client of connectedClients.values().filter((c) => c.connectionStatus === "ready" /* READY */ && !c.dev)) { + const clusters = this.getClusterForConnection(client); + const load = clusters.length; + if (load < lowestLoad) { + lowestLoad = load; + lowestLoadClient = client; + } + } + return lowestLoadClient; + } + getClusterOfShard(shardID) { + return this.clusterList.find((c) => c.shardList.includes(shardID)); + } +}; + +// src/general/ShardingUtil.ts +var ShardingUtil = class { + static getShardIDForGuild(guildID, totalShards) { + if (!guildID || totalShards <= 0) { + throw new Error("Invalid guild ID or total shards"); + } + return Number(BigInt(guildID) >> 22n) % totalShards; + } +}; + +// src/bridge/Bridge.ts +var Bridge = class { + port; + server; + connectedClients = /* @__PURE__ */ new Map(); + token; + intents; + shardsPerCluster = 1; + clusterToStart = 1; + reclusteringTimeoutInMs; + clusterCalculator; + eventMap = { + CLUSTER_READY: void 0, + CLUSTER_HEARTBEAT_FAILED: void 0, + CLUSTER_STOPPED: void 0, + CLIENT_CONNECTED: void 0, + CLIENT_DISCONNECTED: void 0, + CLUSTER_SPAWNED: void 0, + CLUSTER_RECLUSTER: void 0, + ERROR: void 0, + CLIENT_STOP: void 0 + }; + constructor(port, token, intents, shardsPerCluster, clusterToStart, reclusteringTimeoutInMs) { + this.port = port; + this.token = token; + this.intents = intents; + this.clusterToStart = clusterToStart; + this.shardsPerCluster = shardsPerCluster; + this.reclusteringTimeoutInMs = reclusteringTimeoutInMs; + this.clusterCalculator = new ClusterCalculator(this.clusterToStart, this.shardsPerCluster); + this.server = new Server({ + port: this.port + }); + } + start() { + this.server.start().then(() => { + this.startListening(); + }); + this.interval(); + } + interval() { + setInterval(() => { + this.checkCreate(); + this.checkRecluster(); + this.heartbeat(); + }, 5e3); + } + checkRecluster() { + const up = this.clusterCalculator.checkAllClustersConnected(); + if (!up) { + return; + } + const connectedClients = this.connectedClients.values().filter((c) => c.connectionStatus == "ready" /* READY */).filter((c) => !c.dev).filter((c) => c.establishedAt + this.reclusteringTimeoutInMs < Date.now()).toArray(); + const { most, least } = this.clusterCalculator.findMostAndLeastClustersForConnections(connectedClients); + if (most) { + const clusterToSteal = this.clusterCalculator.getClusterForConnection(most)[0] || void 0; + if (least && clusterToSteal) { + clusterToSteal.reclustering(least); + if (this.eventMap.CLUSTER_RECLUSTER) this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection); + this.createCluster(least, clusterToSteal, true); + return; + } + } + } + heartbeat() { + const clusters = this.clusterCalculator.clusterList; + clusters.forEach((cluster) => { + if (cluster.connection && cluster.connectionStatus == "connected" /* CONNECTED */ && !cluster.heartbeatPending) { + cluster.heartbeatPending = true; + cluster.connection.eventManager.request({ + type: "CLUSTER_HEARTBEAT", + data: { + clusterID: cluster.clusterID + } + }, 2e4).then((r) => { + cluster.removeMissedHeartbeat(); + cluster.heartbeatResponse = r; + }).catch((err) => { + if (this.eventMap.CLUSTER_HEARTBEAT_FAILED) this.eventMap.CLUSTER_HEARTBEAT_FAILED(cluster, err); + cluster.addMissedHeartbeat(); + if (cluster.missedHeartbeats > 7 && !cluster.connection?.dev) { + cluster.connection?.eventManager.send({ + type: "CLUSTER_STOP", + data: { + id: cluster.clusterID + } + }); + cluster.connectionStatus = "disconnected" /* DISCONNECTED */; + cluster.resetMissedHeartbeats(); + } + }).finally(() => { + cluster.heartbeatPending = false; + }); + } + }); + } + checkCreate() { + const optionalCluster = this.clusterCalculator.getNextCluster(); + if (!optionalCluster) { + return; + } + const lowestLoadClient = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients); + if (!lowestLoadClient) { + return; + } + this.createCluster(lowestLoadClient, optionalCluster); + } + createCluster(connection, cluster, recluster = false) { + cluster.resetMissedHeartbeats(); + cluster.heartbeatResponse = void 0; + if (!recluster) { + cluster.setConnection(connection); + } else { + cluster.oldConnection?.eventManager.send({ + type: "CLUSTER_RECLUSTER", + data: { + clusterID: cluster.clusterID + } + }); + } + if (this.eventMap.CLUSTER_SPAWNED) this.eventMap.CLUSTER_SPAWNED(cluster, connection); + connection.eventManager.send({ + type: "CLUSTER_CREATE", + data: { + clusterID: cluster.clusterID, + instanceID: connection.instanceID, + totalShards: this.getTotalShards(), + shardList: cluster.shardList, + token: this.token, + intents: this.intents + } + }); + } + startListening() { + this.server.on("connect", (connection, payload) => { + const id = payload?.id; + const data = payload.data; + const dev = payload?.dev || false; + if (!id) { + connection.close("Invalid payload", false); + return; + } + if (this.connectedClients.values().some((client) => client.instanceID === id)) { + connection.close("Already connected", false); + return; + } + const bridgeConnection = new BridgeClientConnection(payload.id, connection, data, dev); + if (this.eventMap.CLIENT_CONNECTED) this.eventMap.CLIENT_CONNECTED(bridgeConnection); + bridgeConnection.onMessage((m2) => { + if (m2.type == "CLUSTER_SPAWNED") { + const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); + if (cluster) { + cluster.connectionStatus = "starting" /* STARTING */; + } + return; + } + if (m2.type == "CLUSTER_READY") { + const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); + if (cluster) { + cluster.startedAt = Date.now(); + if (this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(cluster, m2.data.guilds || 0, m2.data.members || 0); + cluster.connectionStatus = "connected" /* CONNECTED */; + if (cluster.oldConnection) { + cluster.oldConnection.eventManager.send({ + type: "CLUSTER_STOP", + data: { + id: cluster.clusterID + } + }); + cluster.oldConnection = void 0; + } + } + return; + } + if (m2.type == "CLUSTER_STOPPED") { + const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); + if (cluster) { + cluster.startedAt = void 0; + if (this.eventMap.CLUSTER_STOPPED) this.eventMap.CLUSTER_STOPPED(cluster); + cluster.setConnection(void 0); + } + return; + } + if (m2.type == "INSTANCE_STOP") { + this.stopInstance(bridgeConnection); + } + return; + }); + bridgeConnection.onRequest((m2) => { + if (m2.type == "REDIRECT_REQUEST_TO_GUILD") { + const guildID = m2.guildID; + const shardID = ShardingUtil.getShardIDForGuild(guildID, this.getTotalShards()); + const cluster = this.clusterCalculator.getClusterOfShard(shardID); + if (!cluster) { + return Promise.reject(new Error("cluster not found")); + } + if (cluster.connectionStatus != "connected" /* CONNECTED */) { + return Promise.reject(new Error("cluster not connected.")); + } + if (!cluster.connection?.eventManager) { + return Promise.reject(new Error("no connection defined.")); + } + return cluster.connection.eventManager.request({ + type: "REDIRECT_REQUEST_TO_GUILD", + clusterID: cluster.clusterID, + guildID, + data: m2.data + }, 5e3); + } + if (m2.type == "BROADCAST_EVAL") { + const responses = Promise.all( + this.connectedClients.values().map((c) => { + return c.eventManager.request({ + type: "BROADCAST_EVAL", + data: m2.data + }, 5e3); + }) + ); + return new Promise((resolve, reject) => { + responses.then((r) => { + resolve(r.flatMap((f) => f)); + }).catch(reject); + }); + } + if (m2.type == "SELF_CHECK") { + return { + clusterList: [ + ...this.clusterCalculator.getClusterForConnection(bridgeConnection).map((c) => c.clusterID), + ...this.clusterCalculator.getOldClusterForConnection(bridgeConnection).map((c) => c.clusterID) + ] + }; + } + return Promise.reject(new Error("unknown type")); + }); + this.connectedClients.set(connection.id, bridgeConnection); + }); + this.server.on("disconnect", (connection, reason) => { + const closedConnection = this.connectedClients.get(connection.id); + if (!closedConnection) { + return; + } + const clusters = this.clusterCalculator.getClusterForConnection(closedConnection); + for (const cluster of clusters) { + this.clusterCalculator.clearClusterConnection(cluster.clusterID); + } + this.connectedClients.delete(connection.id); + if (this.eventMap.CLIENT_DISCONNECTED) this.eventMap.CLIENT_DISCONNECTED(closedConnection, reason); + }); + this.server.on("message", (message2, connection) => { + this.sendMessageToClient(connection.id, message2); + }); + } + sendMessageToClient(clientId, message2) { + if (!this.connectedClients.has(clientId)) { + return; + } + const client = this.connectedClients.get(clientId); + if (client) { + client.messageReceive(message2); + } + } + getTotalShards() { + return this.shardsPerCluster * this.clusterToStart; + } + on(event, listener) { + this.eventMap[event] = listener; + } + getClusters() { + return this.clusterCalculator.clusterList; + } + async stopAllInstances() { + const instances = Array.from(this.connectedClients.values()); + for (const instance of instances) { + instance.connectionStatus = "pending_stop" /* PENDING_STOP */; + } + for (const instance of instances) { + await this.stopInstance(instance, false); + } + } + async stopAllInstancesWithRestart() { + const instances = Array.from(this.connectedClients.values()); + for (const instance of instances) { + await this.stopInstance(instance); + await new Promise((resolve) => { + setTimeout(async () => { + resolve(); + }, 1e3 * 10); + }); + } + } + async moveCluster(instance, cluster) { + cluster.reclustering(instance); + this.createCluster(instance, cluster, true); + } + async stopInstance(instance, recluster = true) { + if (this.eventMap.CLIENT_STOP) this.eventMap.CLIENT_STOP(instance); + instance.connectionStatus = "pending_stop" /* PENDING_STOP */; + let clusterToSteal; + await instance.eventManager.send({ + type: "INSTANCE_STOP" + }); + if (recluster) { + while ((clusterToSteal = this.clusterCalculator.getClusterForConnection(instance).filter((c) => c.connectionStatus === "connected" /* CONNECTED */ || c.connectionStatus == "starting" /* STARTING */ || c.connectionStatus == "reclustering" /* RECLUSTERING */)[0]) !== void 0) { + if (clusterToSteal.connectionStatus != "connected" /* CONNECTED */) break; + const least = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients); + if (!least) { + if (this.eventMap.ERROR) { + this.eventMap.ERROR("Reclustering failed: No least cluster found."); + } + await instance.eventManager.send({ + type: "CLUSTER_STOP", + data: { + id: clusterToSteal.clusterID + } + }); + clusterToSteal.connection = void 0; + clusterToSteal.connectionStatus = "disconnected" /* DISCONNECTED */; + continue; + } + clusterToSteal.reclustering(least); + if (this.eventMap.CLUSTER_RECLUSTER) { + this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection); + } + this.createCluster(least, clusterToSteal, true); + } + return new Promise((resolve, reject) => { + const interval = setInterval(async () => { + const cluster = this.clusterCalculator.getOldClusterForConnection(instance)[0] || void 0; + if (!cluster) { + clearInterval(interval); + await instance.eventManager.send({ + type: "INSTANCE_STOPPED" + }); + await instance.connection.close("Instance stopped.", false); + resolve(); + return; + } + }, 1e3); + }); + } else { + for (const cluster of this.clusterCalculator.getClusterForConnection(instance)) { + await instance.eventManager.send({ + type: "CLUSTER_STOP", + data: { + id: cluster.clusterID + } + }); + } + await instance.eventManager.send({ + type: "INSTANCE_STOPPED" + }); + await instance.connection.close("Instance stopped.", false); + } + } + sendRequestToGuild(cluster, guildID, data, timeout = 5e3) { + if (!cluster.connection) { + return Promise.reject(new Error("No connection defined for cluster " + cluster.clusterID)); + } + return cluster.connection.eventManager.request({ + type: "REDIRECT_REQUEST_TO_GUILD", + clusterID: cluster.clusterID, + guildID, + data + }, timeout); + } +}; + +// src/cluster/Cluster.ts +import os from "os"; +var Cluster = class _Cluster { + instanceID; + clusterID; + shardList = []; + totalShards; + token; + intents; + eventManager; + client; + onSelfDestruct; + eventMap = { + message: void 0, + request: void 0, + CLUSTER_READY: void 0 + }; + constructor(instanceID, clusterID, shardList, totalShards, token, intents) { + this.instanceID = instanceID; + this.clusterID = clusterID; + this.shardList = shardList; + this.totalShards = totalShards; + this.token = token; + this.intents = intents; + this.eventManager = new EventManager((message2) => { + return new Promise((resolve, reject) => { + if (typeof process.send !== "function") { + reject(new Error("Process does not support sending messages")); + return; + } + process.send?.(message2, void 0, void 0, (error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + }, (message2) => { + this._onMessage(message2); + }, (message2) => { + return this._onRequest(message2); + }); + process.on("message", (message2) => { + this.eventManager.receive(message2); + }); + } + static initial() { + const args = process.env; + if (args.SHARD_LIST == void 0 || args.INSTANCE_ID == void 0 || args.TOTAL_SHARDS == void 0 || args.TOKEN == void 0 || args.INTENTS == void 0 || args.CLUSTER_ID == void 0) { + throw new Error("Missing required environment variables"); + } + const shardList = args.SHARD_LIST.split(",").map(Number); + const totalShards = Number(args.TOTAL_SHARDS); + const instanceID = Number(args.INSTANCE_ID); + const clusterID = Number(args.CLUSTER_ID); + const token = args.TOKEN; + const intents = args.INTENTS.split(",").map((i) => i.trim()); + return new _Cluster(instanceID, clusterID, shardList, totalShards, token, intents); + } + triggerReady(guilds, members) { + this.eventManager.send({ + type: "CLUSTER_READY", + id: this.clusterID, + guilds, + members + }); + if (this.eventMap?.CLUSTER_READY) { + this.eventMap?.CLUSTER_READY(); + } + } + triggerError(e) { + this.eventManager.send({ + type: "CLUSTER_ERROR", + id: this.clusterID + }); + } + async wait(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + _onMessage(message2) { + const m2 = message2; + if (m2.type == "CUSTOM" && this.eventMap.message) { + this.eventMap.message(m2.data); + } + } + _onRequest(message) { + const m = message; + if (m.type == "CUSTOM" && this.eventMap.request) { + return new Promise((resolve, reject) => { + this.eventMap.request(m.data, resolve, reject); + }); + } else if (m.type == "CLUSTER_HEARTBEAT") { + const startTime = process.hrtime.bigint(); + const startUsage = process.cpuUsage(); + (async () => { + await this.wait(500); + })(); + const endTime = process.hrtime.bigint(); + const usageDiff = process.cpuUsage(startUsage); + const elapsedTimeUs = Number((endTime - startTime) / 1000n); + const totalCPUTime = usageDiff.user + usageDiff.system; + const cpuCount = os.cpus().length; + const cpuPercent = totalCPUTime / (elapsedTimeUs * cpuCount) * 100; + let shardPings = []; + try { + const shards = this.client.ws.shards; + if (shards) { + shards.forEach((shard) => { + shardPings.push({ + id: shard.id, + ping: shard.ping, + status: shard.status, + guilds: this.client.guilds.cache.filter((g) => g.shardId === shard.id).size, + members: this.client.guilds.cache.filter((g) => g.shardId === shard.id).reduce((acc, g) => acc + g.memberCount, 0) + }); + this.client.shard?.fetchClientValues("uptime", shard.id).then((values) => { + shardPings[shard.id]["uptime"] = values; + console.log(values); + }).catch((e) => { + }); + }); + } + } catch (_) { + } + return { + cpu: { raw: process.cpuUsage(), cpuPercent: cpuPercent.toFixed(2) }, + memory: { + raw: process.memoryUsage(), + memoryPercent: (process.memoryUsage().heapUsed / process.memoryUsage().heapTotal * 100).toFixed(2) + "%", + usage: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + "MB" + }, + ping: this.client.ws.ping, + shardPings + }; + } else if (m.type == "BROADCAST_EVAL") { + const broadcast = message; + const fn = eval(`(${broadcast.data})`); + const result = fn(this.client); + if (result instanceof Promise) { + return new Promise((resolve, reject) => { + result.then((res) => { + resolve(res); + }).catch((err) => { + reject(err); + }); + }); + } else { + return result; + } + } else if (m.type == "SELF_DESTRUCT") { + if (this.onSelfDestruct) { + this.onSelfDestruct(); + } + } + return void 0; + } + on(event, listener) { + this.eventMap[event] = listener; + } + sendMessage(data) { + this.eventManager.send({ + type: "CUSTOM", + data + }); + } + sendRequest(data, timeout = 5e3) { + return this.eventManager.request({ + type: "CUSTOM", + data + }, timeout); + } + broadcastEval(fn2, timeout = 2e4) { + return this.eventManager.request({ + type: "BROADCAST_EVAL", + data: fn2.toString() + }, timeout); + } + sendMessageToClusterOfGuild(guildID, message2) { + if (this.eventManager) { + this.eventManager.send({ + type: "REDIRECT_MESSAGE_TO_GUILD", + guildID, + data: message2 + }); + } + } + sendRequestToClusterOfGuild(guildID, message2, timeout = 5e3) { + return new Promise((resolve, reject) => { + if (this.eventManager) { + this.eventManager.request({ + type: "REDIRECT_REQUEST_TO_GUILD", + guildID, + data: message2 + }, timeout).then((response) => { + resolve(response); + }).catch((error) => { + reject(error); + }); + } else { + reject(new Error("Event manager is not initialized")); + } + }); + } +}; + +// src/cluster/ClusterProcess.ts +var ClusterProcess = class { + child; + eventManager; + id; + shardList; + totalShards; + status; + createdAt = Date.now(); + _onMessage; + _onRequest; + constructor(id, child, shardList, totalShards) { + this.id = id; + this.child = child; + this.shardList = shardList; + this.totalShards = totalShards; + this.status = "starting"; + this.eventManager = new EventManager((message2) => { + return new Promise((resolve, reject) => { + this.child.send(message2, (error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + }, (message2) => { + if (this._onMessage) { + this._onMessage(message2); + } + }, (message2) => { + if (this._onRequest) { + return this._onRequest(message2); + } + return void 0; + }); + this.child.on("message", (message2) => { + this.eventManager.receive(message2); + }); + this.child.on("exit", () => { + this.eventManager.close("child process exited"); + }); + this.child.on("error", () => { + this.eventManager.close("child process error"); + }); + } + onMessage(callback) { + this._onMessage = callback; + } + onRequest(callback) { + this._onRequest = callback; + } + sendMessage(data) { + this.eventManager.send({ + type: "CUSTOM", + data + }); + } + sendRequest(data, timeout = 5e3) { + return this.eventManager.request({ + type: "CUSTOM", + data + }, timeout); + } +}; + +// src/instance/BotInstance.ts +import { fork } from "child_process"; +var BotInstance = class { + entryPoint; + execArgv; + clients = /* @__PURE__ */ new Map(); + constructor(entryPoint, execArgv) { + this.entryPoint = entryPoint; + this.execArgv = execArgv ?? []; + } + eventMap = { + "message": void 0, + "request": void 0, + "PROCESS_KILLED": void 0, + "PROCESS_SELF_DESTRUCT_ERROR": void 0, + "PROCESS_SPAWNED": void 0, + "ERROR": void 0, + "PROCESS_ERROR": void 0, + "CLUSTER_READY": void 0, + "CLUSTER_ERROR": void 0, + "CLUSTER_RECLUSTER": void 0, + "BRIDGE_CONNECTION_ESTABLISHED": void 0, + "BRIDGE_CONNECTION_CLOSED": void 0, + "BRIDGE_CONNECTION_STATUS_CHANGE": void 0, + "INSTANCE_STOP": void 0, + "INSTANCE_STOPPED": void 0, + "SELF_CHECK_SUCCESS": void 0, + "SELF_CHECK_ERROR": void 0, + "SELF_CHECK_RECEIVED": void 0 + }; + startProcess(instanceID, clusterID, shardList, totalShards, token, intents) { + try { + const child = fork(this.entryPoint, { + env: { + INSTANCE_ID: instanceID.toString(), + CLUSTER_ID: clusterID.toString(), + SHARD_LIST: shardList.join(","), + TOTAL_SHARDS: totalShards.toString(), + TOKEN: token, + INTENTS: intents.join(","), + FORCE_COLOR: "true" + }, + stdio: "inherit", + execArgv: this.execArgv, + silent: false, + detached: true + }); + const client = new ClusterProcess(clusterID, child, shardList, totalShards); + child.stdout?.on("data", (data) => { + process.stdout.write(data); + }); + child.stderr?.on("data", (data) => { + process.stderr.write(data); + }); + child.on("spawn", () => { + if (this.eventMap.PROCESS_SPAWNED) this.eventMap.PROCESS_SPAWNED(client); + this.setClusterSpawned(client); + this.clients.set(clusterID, client); + client.onMessage((message2) => { + this.onMessage(client, message2); + }); + client.onRequest((message2) => { + return this.onRequest(client, message2); + }); + }); + child.on("error", (err) => { + if (this.eventMap.PROCESS_ERROR) this.eventMap.PROCESS_ERROR(client, err); + }); + child.on("exit", (err) => { + if (client.status !== "stopped") { + client.status = "stopped"; + this.killProcess(client, `Process exited: ${err?.message}`); + } + }); + } catch (error) { + throw new Error(`Failed to start process for cluster ${clusterID}: ${error instanceof Error ? error.message : String(error)}`); + } + } + killProcess(client, reason) { + client.status = "stopped"; + client.eventManager.request({ + type: "SELF_DESTRUCT", + reason + }, 5e3).catch(() => { + if (this.eventMap.PROCESS_SELF_DESTRUCT_ERROR) this.eventMap.PROCESS_SELF_DESTRUCT_ERROR(client, reason, "Cluster didnt respond to shot-call."); + }).finally(() => { + if (client.child && client.child.pid) { + if (client.child.kill("SIGKILL")) { + if (this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, true); + } else { + if (this.eventMap.ERROR) this.eventMap.ERROR(`Failed to kill process for cluster ${client.id}`); + client.child.kill("SIGKILL"); + } + try { + process.kill(-client.child.pid); + } catch { + } + } else { + if (this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, false); + } + this.clients.delete(client.id); + this.setClusterStopped(client, reason); + }); + } + onMessage(client, message2) { + if (message2.type === "CLUSTER_READY") { + client.status = "running"; + if (this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(client); + this.setClusterReady(client, message2.guilds || 0, message2.members || 0); + } + if (message2.type === "CLUSTER_ERROR") { + client.status = "stopped"; + if (this.eventMap.CLUSTER_ERROR) this.eventMap.CLUSTER_ERROR(client, message2.error); + this.killProcess(client, "Cluster error: " + message2.error); + } + if (message2.type == "CUSTOM" && this.eventMap.message) { + this.eventMap.message(client, message2.data); + } + } + on(event, listener) { + this.eventMap[event] = listener; + } + sendRequestToClusterOfGuild(guildID, message2, timeout = 5e3) { + return new Promise((resolve, reject) => { + for (const client of this.clients.values()) { + const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); + if (client.shardList.includes(shardID)) { + client.eventManager.request({ + type: "CUSTOM", + data: message2 + }, timeout).then(resolve).catch(reject); + return; + } + } + reject(new Error(`No cluster found for guild ${guildID}`)); + }); + } + sendRequestToCluster(cluster, message2, timeout = 5e3) { + return new Promise((resolve, reject) => { + cluster.eventManager.request({ + type: "CUSTOM", + data: message2 + }, timeout).then(resolve).catch(reject); + return; + }); + } +}; + +// src/instance/ManagedInstance.ts +import { Client } from "net-ipc"; +var BridgeConnectionStatus = /* @__PURE__ */ ((BridgeConnectionStatus2) => { + BridgeConnectionStatus2[BridgeConnectionStatus2["CONNECTED"] = 0] = "CONNECTED"; + BridgeConnectionStatus2[BridgeConnectionStatus2["DISCONNECTED"] = 1] = "DISCONNECTED"; + return BridgeConnectionStatus2; +})(BridgeConnectionStatus || {}); +var ManagedInstance = class extends BotInstance { + host; + port; + instanceID; + eventManager; + connectionStatus = 1 /* DISCONNECTED */; + data; + dev = false; + constructor(entryPoint, host, port, instanceID, data, execArgv, dev) { + super(entryPoint, execArgv); + this.host = host; + this.port = port; + this.instanceID = instanceID; + this.data = data; + this.dev = dev || false; + } + start() { + const client = new Client({ + host: this.host, + port: this.port, + reconnect: true, + retries: 100 + }); + this.eventManager = new EventManager((message2) => { + if (client.status == 3) { + return client.send(message2); + } + return Promise.reject(new Error("Client is not ready to send messages")); + }, (message2) => { + const m2 = message2; + if (m2.type == "CLUSTER_CREATE") { + this.onClusterCreate(m2.data); + } else if (m2.type == "CLUSTER_STOP") { + this.onClusterStop(m2.data); + } else if (m2.type == "CLUSTER_RECLUSTER") { + this.onClusterRecluster(m2.data); + } else if (m2.type == "INSTANCE_STOP") { + if (this.eventMap.INSTANCE_STOP) { + this.eventMap.INSTANCE_STOP(); + } + } else if (m2.type == "INSTANCE_STOPPED") { + if (this.eventMap.INSTANCE_STOPPED) { + this.eventMap.INSTANCE_STOPPED(); + } + } + }, (message2) => { + return this.onBridgeRequest(message2); + }); + setInterval(() => { + if (this.connectionStatus == 0 /* CONNECTED */) { + this.selfCheck(); + } + }, 2500); + client.connect({ + id: this.instanceID, + dev: this.dev, + data: this.data + }).then((_) => { + if (this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED(); + this.connectionStatus = 0 /* CONNECTED */; + client.on("message", (message2) => { + this.eventManager?.receive(message2); + }); + client.on("close", (reason) => { + if (this.eventMap.BRIDGE_CONNECTION_CLOSED) this.eventMap.BRIDGE_CONNECTION_CLOSED(reason); + if (this.connectionStatus == 0 /* CONNECTED */) { + this.clients.forEach((client2) => { + this.killProcess(client2, "Bridge connection closed"); + }); + } + this.connectionStatus = 1 /* DISCONNECTED */; + }); + client.on("status", (status) => { + if (this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE) this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE(status); + if (status == 4) { + if (this.connectionStatus == 0 /* CONNECTED */) { + this.clients.forEach((client2) => { + this.killProcess(client2, "Bridge connection closed"); + }); + } + this.connectionStatus = 1 /* DISCONNECTED */; + } else if (status == 3) { + this.connectionStatus = 0 /* CONNECTED */; + if (this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED(); + } + }); + }); + } + selfCheck() { + this.eventManager.request({ + type: "SELF_CHECK" + }, 1e3 * 60).then((r) => { + const response = r; + if (this.eventMap.SELF_CHECK_RECEIVED) { + this.eventMap.SELF_CHECK_RECEIVED(response); + } + const startingClusters = this.clients.values().filter((c) => c.status == "starting").toArray(); + startingClusters.forEach((c) => { + if (Date.now() - c.createdAt > 10 * 60 * 1e3) { + this.killProcess(c, "Cluster took too long to start"); + } + }); + const wrongClusters = this.clients.values().filter((c) => !response.clusterList.includes(c.id)).toArray(); + if (wrongClusters.length > 0) { + if (this.eventMap.SELF_CHECK_ERROR) { + this.eventMap.SELF_CHECK_ERROR(`Self check found wrong clusters: ${wrongClusters.map((c) => c.id).join(", ")}`); + } + wrongClusters.forEach((c) => { + this.killProcess(c, "Self check found wrong cluster"); + }); + } else { + if (this.eventMap.SELF_CHECK_SUCCESS) { + this.eventMap.SELF_CHECK_SUCCESS(); + } + } + }).catch((err) => { + if (this.eventMap.SELF_CHECK_ERROR) { + this.eventMap.SELF_CHECK_ERROR(`Self check failed: ${err}`); + } + }); + } + setClusterStopped(client, reason) { + this.eventManager?.send({ + type: "CLUSTER_STOPPED", + data: { + id: client.id, + reason + } + }).catch(() => { + return null; + }); + } + setClusterReady(client, guilds, members) { + this.eventManager?.send({ + type: "CLUSTER_READY", + data: { + id: client.id, + guilds, + members + } + }); + } + setClusterSpawned(client) { + this.eventManager?.send({ + type: "CLUSTER_SPAWNED", + data: { + id: client.id + } + }); + } + onClusterCreate(message2) { + const m2 = message2; + if (this.clients.has(m2.clusterID)) { + this.eventManager?.send({ + type: "CLUSTER_STOPPED", + data: { + id: m2.clusterID, + reason: "Cluster already exists" + } + }).catch(() => { + return null; + }); + return; + } + this.startProcess(this.instanceID, m2.clusterID, m2.shardList, m2.totalShards, m2.token, m2.intents); + } + onClusterStop(message2) { + const m2 = message2; + const cluster = this.clients.get(m2.id); + if (cluster) { + this.killProcess(cluster, `Request to stop cluster ${m2.id}`); + } + } + onClusterRecluster(message2) { + const m2 = message2; + const cluster = this.clients.get(m2.clusterID); + if (this.eventMap.CLUSTER_RECLUSTER && cluster) { + this.eventMap.CLUSTER_RECLUSTER(cluster); + } + } + onRequest(client, message2) { + if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { + const guildID = message2.guildID; + const data = message2.data; + const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); + if (client.shardList.includes(shardID)) { + return client.eventManager.request({ + type: "CUSTOM", + data + }, 5e3); + } else { + return this.eventManager.request({ + type: "REDIRECT_REQUEST_TO_GUILD", + guildID, + data + }, 5e3); + } + } + if (message2.type == "BROADCAST_EVAL") { + return this.eventManager.request({ + type: "BROADCAST_EVAL", + data: message2.data + }, 5e3); + } + if (message2.type == "CUSTOM" && this.eventMap.request) { + return new Promise((resolve, reject) => { + this.eventMap.request(client, message2.data, resolve, reject); + }); + } + return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); + } + onBridgeRequest(message2) { + if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { + const clusterID = message2.clusterID; + const data = message2.data; + const cluster = this.clients.get(clusterID); + if (cluster) { + return cluster.eventManager.request({ + type: "CUSTOM", + data + }, 5e3); + } else { + return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`)); + } + } else if (message2.type == "CLUSTER_HEARTBEAT") { + const clusterID = message2.data.clusterID; + const cluster = this.clients.get(clusterID); + if (cluster) { + return new Promise((resolve, reject) => { + cluster.eventManager.request({ + type: "CLUSTER_HEARTBEAT" + }, 15e3).then((r) => { + resolve(r); + }).catch((err) => { + reject(err); + }); + }); + } else { + return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`)); + } + } else if (message2.type == "BROADCAST_EVAL") { + return Promise.all(this.clients.values().filter((c) => c.status == "running").map((c) => { + return c.eventManager.request({ + type: "BROADCAST_EVAL", + data: message2.data + }, 5e3); + })); + } + return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); + } + stopInstance() { + this.eventManager?.send({ + type: "INSTANCE_STOP" + }); + } +}; + +// src/instance/StandaloneInstance.ts +var StandaloneInstance = class extends BotInstance { + totalClusters; + shardsPerCluster; + token; + intents; + constructor(entryPoint, shardsPerCluster, totalClusters, token, intents, execArgv) { + super(entryPoint, execArgv); + this.shardsPerCluster = shardsPerCluster; + this.totalClusters = totalClusters; + this.token = token; + this.intents = intents; + } + get totalShards() { + return this.shardsPerCluster * this.totalClusters; + } + calculateClusters() { + const clusters = {}; + for (let i = 0; i < this.totalClusters; i++) { + clusters[i] = []; + for (let j = 0; j < this.shardsPerCluster; j++) { + clusters[i].push(i * this.shardsPerCluster + j); + } + } + return clusters; + } + start() { + const clusters = this.calculateClusters(); + for (const [id, shardList] of Object.entries(clusters)) { + this.startProcess(1, Number(id), shardList, this.totalShards, this.token, this.intents); + } + } + setClusterStopped(client, reason) { + this.clients.delete(client.id); + this.restartProcess(client); + } + setClusterReady(client) { + } + setClusterSpawned(client) { + } + restartProcess(client) { + this.startProcess(1, client.id, client.shardList, this.totalShards, this.token, this.intents); + } + onRequest(client, message2) { + if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { + const guildID = message2.guildID; + const data = message2.data; + const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); + if (client.shardList.includes(shardID)) { + return client.eventManager.request({ + type: "CUSTOM", + data + }, 5e3); + } else { + return Promise.reject(new Error(`Shard ID ${shardID} not found in cluster ${client.id} for guild ${guildID}`)); + } + } + if (message2.type == "BROADCAST_EVAL") { + return Promise.all( + this.clients.values().map((c) => { + return c.eventManager.request({ + type: "BROADCAST_EVAL", + data: message2.data + }, 5e3); + }) + ); + } + if (message2.type == "CUSTOM" && this.eventMap.request) { + return new Promise((resolve, reject) => { + this.eventMap.request(client, message2.data, resolve, reject); + }); + } + return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); + } +}; +export { + BotInstance, + Bridge, + BridgeClientCluster, + BridgeClientClusterConnectionStatus, + BridgeClientConnection, + BridgeClientConnectionStatus, + BridgeConnectionStatus, + Cluster, + ClusterCalculator, + ClusterProcess, + EventManager, + ManagedInstance, + ShardingUtil, + StandaloneInstance +}; +//# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/dist/index.mjs.map b/dist/index.mjs.map new file mode 100644 index 0000000..133deee --- /dev/null +++ b/dist/index.mjs.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/bridge/BridgeClientCluster.ts","../src/general/EventManager.ts","../src/bridge/BridgeClientConnection.ts","../src/bridge/Bridge.ts","../src/bridge/ClusterCalculator.ts","../src/general/ShardingUtil.ts","../src/cluster/Cluster.ts","../src/cluster/ClusterProcess.ts","../src/instance/BotInstance.ts","../src/instance/ManagedInstance.ts","../src/instance/StandaloneInstance.ts"],"sourcesContent":["import {BridgeClientConnection} from \"./BridgeClientConnection\";\n\nexport enum BridgeClientClusterConnectionStatus {\n REQUESTING = 'requesting',\n STARTING = 'starting',\n CONNECTED = 'connected',\n RECLUSTERING = 'reclustering',\n DISCONNECTED = 'disconnected',\n}\n\nexport class BridgeClientCluster {\n public readonly clusterID: number;\n public readonly shardList: number[];\n public connectionStatus: BridgeClientClusterConnectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n\n public connection?: BridgeClientConnection;\n\n public oldConnection?: BridgeClientConnection;\n\n public missedHeartbeats: number = 0;\n\n public heartbeatResponse?: HeartbeatResponse;\n\n public heartbeatPending = false;\n\n public startedAt?: number;\n\n constructor(clusterID: number, shardList: number[]) {\n this.clusterID = clusterID;\n this.shardList = shardList;\n }\n\n setConnection(connection?: BridgeClientConnection): void {\n if(connection == undefined){\n this.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n this.connection = undefined;\n return;\n }\n\n if (this.connection) {\n throw new Error(`Connection already set for cluster ${this.clusterID}`);\n }\n\n this.connectionStatus = BridgeClientClusterConnectionStatus.REQUESTING;\n this.connection = connection;\n }\n\n setOldConnection(connection?: BridgeClientConnection): void {\n this.oldConnection = connection;\n }\n\n isUsed(): boolean {\n return this.connection != undefined && this.connectionStatus !== BridgeClientClusterConnectionStatus.DISCONNECTED;\n }\n\n reclustering(connection: BridgeClientConnection): void {\n this.connectionStatus = BridgeClientClusterConnectionStatus.RECLUSTERING;\n this.oldConnection = this.connection;\n this.connection = connection;\n }\n\n addMissedHeartbeat(): void {\n this.missedHeartbeats++;\n }\n\n removeMissedHeartbeat(): void {\n if (this.missedHeartbeats > 0) {\n this.missedHeartbeats--;\n }\n }\n\n resetMissedHeartbeats(): void {\n this.missedHeartbeats = 0;\n }\n}\n\nexport type HeartbeatResponse = {\n cpu: {\n raw: {\n user: number,\n system: number,\n }\n cpuPercent: string\n },\n memory: {\n raw: {\n rss: number,\n heapTotal: number,\n heapUsed: number,\n external: number,\n arrayBuffers: number,\n },\n memoryPercent: string\n usage: number\n },\n ping: number,\n shardPings: {\n id: number,\n ping: number,\n status: number,\n guilds: number,\n members: number\n }[]\n}","import {EventPayload} from \"./EventPayload\";\n\nexport class EventManager {\n\n private pendingPayloads = new Map void;\n reject: (error: unknown) => void;\n }>();\n\n // Track per-request timeout handles so we can clear them on resolve/reject\n private pendingTimeouts = new Map>();\n\n private readonly _send: (payload: EventPayload) => Promise;\n\n private readonly _on: (payload: unknown) => void;\n\n private readonly _request: (payload: unknown) => unknown;\n\n constructor(send: (payload: EventPayload) => Promise, on: (message: unknown) => void, request: (message: unknown) => unknown) {\n this._send = send;\n this._on = on;\n this._request = request\n }\n\n async send(data: unknown) {\n return this._send({\n id: crypto.randomUUID(),\n type: 'message',\n data: data\n });\n }\n\n async request(payload: unknown, timeout: number): Promise {\n const id = crypto.randomUUID();\n\n return new Promise((resolve, reject) => {\n this._send({\n id: id,\n type: 'request',\n data: payload\n });\n\n this.pendingPayloads.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject\n });\n\n const t = setTimeout(() => {\n if (this.pendingPayloads.has(id)) {\n this.pendingPayloads.delete(id);\n this.pendingTimeouts.delete(id);\n reject({\n error: `Request with id ${id} timed out`,\n });\n }\n }, timeout);\n this.pendingTimeouts.set(id, t);\n })\n }\n\n receive(possiblePayload: unknown) {\n if (typeof possiblePayload !== 'object' || possiblePayload === null) {\n return;\n }\n\n const payload = possiblePayload as EventPayload;\n\n if (!payload.id || !payload.type) {\n return;\n }\n\n if (payload.type === 'message') {\n this._on(payload.data);\n return;\n }\n\n if (payload.type === 'response') {\n // Handle requests\n const resolve = this.pendingPayloads.get(payload.id)?.resolve;\n if (resolve) {\n resolve(payload.data);\n this.pendingPayloads.delete(payload.id);\n const to = this.pendingTimeouts.get(payload.id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(payload.id);\n }\n return;\n }\n\n if (payload.type === 'response_error') {\n // Handle requests\n const reject = this.pendingPayloads.get(payload.id)?.reject;\n if (reject) {\n reject(payload.data);\n this.pendingPayloads.delete(payload.id);\n const to = this.pendingTimeouts.get(payload.id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(payload.id);\n }\n return;\n }\n\n if (payload.type === 'request') {\n // Handle requests\n const data = this._request(payload.data);\n if(data instanceof Promise) {\n data.then((result) => {\n this._send({\n id: payload.id,\n type: 'response',\n data: result\n });\n }).catch((error) => {\n this._send({\n id: payload.id,\n type: 'response_error',\n data: error\n });\n });\n } else {\n this._send({\n id: payload.id,\n type: 'response',\n data: data\n });\n }\n return;\n }\n }\n\n // Reject and clear all pending requests to avoid memory leaks when a connection/process closes\n close(reason?: string) {\n if (this.pendingPayloads.size === 0 && this.pendingTimeouts.size === 0) return;\n const err = { error: reason || 'EventManager closed' };\n for (const [id, handlers] of this.pendingPayloads.entries()) {\n try { handlers.reject(err); } catch (_) { /* ignore */ }\n this.pendingPayloads.delete(id);\n const to = this.pendingTimeouts.get(id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(id);\n }\n // In case there are any stray timeouts with no pending payload\n for (const to of this.pendingTimeouts.values()) {\n clearTimeout(to);\n }\n this.pendingTimeouts.clear();\n }\n}\n\n","import {EventManager} from \"../general/EventManager\";\nimport {Connection} from \"net-ipc\";\n\nexport enum BridgeClientConnectionStatus {\n READY = 'ready',\n PENDING_STOP = 'pending_stop',\n}\nexport class BridgeClientConnection {\n public readonly instanceID: number;\n public readonly eventManager: EventManager;\n public readonly connection: Connection;\n public readonly data: unknown;\n public connectionStatus: BridgeClientConnectionStatus = BridgeClientConnectionStatus.READY;\n public readonly dev: boolean = false;\n public readonly establishedAt: number = Date.now();\n\n private _onMessage?: (message: unknown) => void;\n private _onRequest?: (message: unknown) => unknown;\n\n constructor(instanceID: number, connection: Connection, data: unknown, dev: boolean) {\n this.instanceID = instanceID;\n this.connection = connection;\n this.data = data;\n this.dev = dev || false;\n this.eventManager = new EventManager((message) => {\n if(!this.connection?.connection?.closed){\n return this.connection.send(message);\n }\n return Promise.reject(new Error('Connection is closed, cannot send message'));\n }, (message) => {\n if (this._onMessage) {\n this._onMessage(message);\n }\n }, (message) => {\n if (this._onRequest) {\n return this._onRequest(message);\n }\n return undefined;\n })\n }\n\n messageReceive(message: any) {\n this.eventManager.receive(message);\n }\n\n onRequest(callback: (message: unknown) => unknown) {\n this._onRequest = callback;\n }\n\n onMessage(callback: (message: unknown) => void) {\n this._onMessage = callback;\n }\n}","import {Server} from 'net-ipc';\nimport {BridgeClientConnection, BridgeClientConnectionStatus} from \"./BridgeClientConnection\";\nimport {GatewayIntentsString, Snowflake} from \"discord.js\";\nimport {ClusterCalculator} from \"./ClusterCalculator\";\nimport {BridgeClientCluster, BridgeClientClusterConnectionStatus, HeartbeatResponse} from \"./BridgeClientCluster\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport class Bridge {\n public readonly port: number;\n public readonly server: Server;\n public readonly connectedClients: Map = new Map();\n private readonly token: string;\n private readonly intents: GatewayIntentsString[];\n private readonly shardsPerCluster: number = 1;\n private readonly clusterToStart: number = 1\n private readonly reclusteringTimeoutInMs: number;\n\n private readonly clusterCalculator: ClusterCalculator;\n\n private readonly eventMap: BridgeEventListeners = {\n CLUSTER_READY: undefined, CLUSTER_HEARTBEAT_FAILED: undefined,\n CLUSTER_STOPPED: undefined, CLIENT_CONNECTED: undefined, CLIENT_DISCONNECTED: undefined,\n CLUSTER_SPAWNED: undefined, CLUSTER_RECLUSTER: undefined, ERROR: undefined,\n CLIENT_STOP: undefined\n }\n\n constructor(port: number, token: string, intents: GatewayIntentsString[], shardsPerCluster: number, clusterToStart: number, reclusteringTimeoutInMs: number) {\n this.port = port;\n this.token = token;\n this.intents = intents;\n this.clusterToStart = clusterToStart;\n this.shardsPerCluster = shardsPerCluster;\n this.reclusteringTimeoutInMs = reclusteringTimeoutInMs;\n\n this.clusterCalculator = new ClusterCalculator(this.clusterToStart, this.shardsPerCluster);\n\n this.server = new Server({\n port: this.port,\n })\n }\n\n public start(): void {\n this.server.start().then(() => {\n this.startListening();\n })\n\n this.interval();\n }\n\n private interval(): void {\n setInterval(() => {\n this.checkCreate();\n this.checkRecluster();\n this.heartbeat();\n }, 5000)\n }\n\n private checkRecluster(): void {\n // check if all clusters are used\n const up = this.clusterCalculator.checkAllClustersConnected()\n if (!up) {\n return;\n }\n\n const connectedClients: BridgeClientConnection[] = this.connectedClients.values()\n .filter(c => c.connectionStatus == BridgeClientConnectionStatus.READY)\n .filter(c => !c.dev)\n .filter(c => c.establishedAt + this.reclusteringTimeoutInMs < Date.now())\n .toArray();\n\n const {most, least} = this.clusterCalculator.findMostAndLeastClustersForConnections(connectedClients);\n if (most) {\n const clusterToSteal = this.clusterCalculator.getClusterForConnection(most)[0] || undefined;\n if (least && clusterToSteal) {\n clusterToSteal.reclustering(least);\n\n if(this.eventMap.CLUSTER_RECLUSTER) this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection!);\n this.createCluster(least, clusterToSteal, true);\n\n return;\n }\n }\n }\n\n private heartbeat(): void {\n const clusters = this.clusterCalculator.clusterList;\n\n clusters.forEach((cluster) => {\n if(cluster.connection && cluster.connectionStatus == BridgeClientClusterConnectionStatus.CONNECTED && !cluster.heartbeatPending) {\n cluster.heartbeatPending = true;\n cluster.connection.eventManager.request({\n type: 'CLUSTER_HEARTBEAT',\n data: {\n clusterID: cluster.clusterID\n }\n }, 20000).then((r) => {\n cluster.removeMissedHeartbeat();\n cluster.heartbeatResponse = r;\n }).catch((err) => {\n if(this.eventMap.CLUSTER_HEARTBEAT_FAILED) this.eventMap.CLUSTER_HEARTBEAT_FAILED(cluster, err)\n cluster.addMissedHeartbeat()\n\n if(cluster.missedHeartbeats > 7 && !cluster.connection?.dev){\n cluster.connection?.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n cluster.resetMissedHeartbeats()\n }\n }).finally(() => {\n cluster.heartbeatPending = false;\n })\n }\n });\n }\n\n private checkCreate(): void {\n const optionalCluster = this.clusterCalculator.getNextCluster();\n\n if (!optionalCluster) {\n return;\n }\n\n const lowestLoadClient = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients);\n if (!lowestLoadClient) {\n return;\n }\n\n this.createCluster(lowestLoadClient, optionalCluster)\n }\n\n private createCluster(connection: BridgeClientConnection, cluster: BridgeClientCluster, recluster = false) {\n cluster.resetMissedHeartbeats()\n cluster.heartbeatResponse = undefined;\n if (!recluster) {\n cluster.setConnection(connection)\n } else {\n cluster.oldConnection?.eventManager.send({\n type: 'CLUSTER_RECLUSTER',\n data: {\n clusterID: cluster.clusterID\n }\n })\n }\n if(this.eventMap.CLUSTER_SPAWNED) this.eventMap.CLUSTER_SPAWNED(cluster, connection)\n connection.eventManager.send({\n type: 'CLUSTER_CREATE',\n data: {\n clusterID: cluster.clusterID,\n instanceID: connection.instanceID,\n totalShards: this.getTotalShards(),\n shardList: cluster.shardList,\n token: this.token,\n intents: this.intents\n }\n });\n }\n\n public startListening(): void {\n this.server.on('connect', (connection, payload) => {\n const id = payload?.id;\n const data = payload.data as unknown;\n const dev = payload?.dev || false;\n if (!id) {\n connection.close('Invalid payload', false);\n return;\n }\n\n if (this.connectedClients.values().some(client => client.instanceID === id)) {\n connection.close('Already connected', false);\n return;\n }\n\n const bridgeConnection = new BridgeClientConnection(payload.id, connection, data, dev);\n if(this.eventMap.CLIENT_CONNECTED) this.eventMap.CLIENT_CONNECTED(bridgeConnection);\n\n bridgeConnection.onMessage((m: any) => {\n if (m.type == 'CLUSTER_SPAWNED') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.STARTING;\n }\n return;\n }\n\n if (m.type == 'CLUSTER_READY') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.startedAt = Date.now();\n if(this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(cluster, m.data.guilds || 0, m.data.members || 0);\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.CONNECTED;\n if (cluster.oldConnection) {\n cluster.oldConnection.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n cluster.oldConnection = undefined;\n }\n }\n return;\n }\n\n if (m.type == 'CLUSTER_STOPPED') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.startedAt = undefined;\n if(this.eventMap.CLUSTER_STOPPED) this.eventMap.CLUSTER_STOPPED(cluster);\n cluster.setConnection(undefined);\n }\n return;\n }\n\n if(m.type == \"INSTANCE_STOP\") {\n this.stopInstance(bridgeConnection);\n }\n\n return;\n })\n\n bridgeConnection.onRequest((m: any) => {\n if(m.type == 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = m.guildID;\n const shardID = ShardingUtil.getShardIDForGuild(guildID, this.getTotalShards());\n const cluster = this.clusterCalculator.getClusterOfShard(shardID);\n if(!cluster){\n return Promise.reject(new Error(\"cluster not found\"))\n }\n if(cluster.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED){\n return Promise.reject(new Error(\"cluster not connected.\"))\n }\n\n if(!cluster.connection?.eventManager){\n return Promise.reject(new Error(\"no connection defined.\"))\n }\n\n return cluster.connection.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n clusterID: cluster.clusterID,\n guildID: guildID,\n data: m.data\n }, 5000)\n }\n\n if(m.type == 'BROADCAST_EVAL') {\n const responses = Promise.all(\n this.connectedClients.values().map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: m.data,\n }, 5000);\n })\n )\n return new Promise((resolve, reject) => {\n responses.then((r) => {\n resolve(r.flatMap(f => f))\n }).catch(reject);\n })\n }\n\n if(m.type == 'SELF_CHECK') {\n return {\n clusterList: [\n ...this.clusterCalculator.getClusterForConnection(bridgeConnection).map(c => c.clusterID),\n ...this.clusterCalculator.getOldClusterForConnection(bridgeConnection).map(c => c.clusterID)\n ]\n }\n }\n\n return Promise.reject(new Error(\"unknown type\"))\n })\n\n this.connectedClients.set(connection.id, bridgeConnection)\n });\n\n this.server.on('disconnect', (connection, reason) => {\n const closedConnection = this.connectedClients.get(connection.id);\n if (!closedConnection) {\n return;\n }\n\n const clusters = this.clusterCalculator.getClusterForConnection(closedConnection);\n for (const cluster of clusters) {\n this.clusterCalculator.clearClusterConnection(cluster.clusterID);\n }\n\n this.connectedClients.delete(connection.id);\n if(this.eventMap.CLIENT_DISCONNECTED) this.eventMap.CLIENT_DISCONNECTED(closedConnection, reason);\n });\n\n this.server.on(\"message\", (message, connection) => {\n this.sendMessageToClient(connection.id, message);\n })\n }\n\n sendMessageToClient(clientId: string, message: unknown): void {\n if (!this.connectedClients.has(clientId)) {\n return;\n }\n\n const client = this.connectedClients.get(clientId);\n if (client) {\n client.messageReceive(message);\n }\n }\n\n private getTotalShards() {\n return this.shardsPerCluster * this.clusterToStart;\n }\n\n\n public on(event: K, listener: BridgeEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public getClusters() {\n return this.clusterCalculator.clusterList;\n }\n\n async stopAllInstances() {\n const instances = Array.from(this.connectedClients.values());\n for (const instance of instances) {\n instance.connectionStatus = BridgeClientConnectionStatus.PENDING_STOP;\n }\n\n for (const instance of instances) {\n await this.stopInstance(instance, false);\n }\n }\n\n async stopAllInstancesWithRestart() {\n const instances = Array.from(this.connectedClients.values());\n\n for (const instance of instances) {\n await this.stopInstance(instance);\n await new Promise((resolve) => {\n setTimeout(async () => {\n resolve();\n }, 1000 * 10);\n })\n }\n }\n\n async moveCluster(instance: BridgeClientConnection, cluster: BridgeClientCluster) {\n cluster.reclustering(instance);\n\n this.createCluster(instance, cluster, true);\n }\n\n async stopInstance(instance: BridgeClientConnection, recluster = true) {\n if(this.eventMap.CLIENT_STOP) this.eventMap.CLIENT_STOP(instance);\n instance.connectionStatus = BridgeClientConnectionStatus.PENDING_STOP;\n\n let clusterToSteal: BridgeClientCluster | undefined;\n\n await instance.eventManager.send({\n type: 'INSTANCE_STOP'\n });\n\n if(recluster) {\n while ((clusterToSteal = this.clusterCalculator.getClusterForConnection(instance).filter(c =>\n c.connectionStatus === BridgeClientClusterConnectionStatus.CONNECTED ||\n c.connectionStatus == BridgeClientClusterConnectionStatus.STARTING ||\n c.connectionStatus == BridgeClientClusterConnectionStatus.RECLUSTERING)[0]) !== undefined) {\n // skip if the cluster is not connected\n if(clusterToSteal.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED) break;\n\n const least = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients);\n if (!least) {\n if (this.eventMap.ERROR) {\n this.eventMap.ERROR(\"Reclustering failed: No least cluster found.\");\n }\n await instance.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: clusterToSteal.clusterID\n }\n });\n clusterToSteal.connection = undefined;\n clusterToSteal.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n continue;\n }\n\n clusterToSteal.reclustering(least);\n\n if (this.eventMap.CLUSTER_RECLUSTER) {\n this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection!);\n }\n\n this.createCluster(least, clusterToSteal, true);\n }\n\n return new Promise((resolve, reject) => {\n const interval = setInterval(async () => {\n const cluster = this.clusterCalculator.getOldClusterForConnection(instance)[0] || undefined;\n if (!cluster) {\n clearInterval(interval);\n await instance.eventManager.send({\n type: 'INSTANCE_STOPPED'\n })\n await instance.connection.close(\"Instance stopped.\", false);\n resolve();\n return;\n }\n }, 1000);\n })\n } else {\n for(const cluster of this.clusterCalculator.getClusterForConnection(instance)) {\n await instance.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n }\n\n\n await instance.eventManager.send({\n type: 'INSTANCE_STOPPED'\n })\n await instance.connection.close(\"Instance stopped.\", false);\n }\n }\n\n sendRequestToGuild(cluster: BridgeClientCluster, guildID: Snowflake, data: unknown, timeout = 5000): Promise {\n if(!cluster.connection){\n return Promise.reject(new Error(\"No connection defined for cluster \" + cluster.clusterID));\n }\n\n return cluster.connection.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n clusterID: cluster.clusterID,\n guildID: guildID,\n data: data\n }, timeout);\n }\n}\n\n\n\nexport type BridgeEventListeners = {\n 'CLUSTER_READY': ((cluster: BridgeClientCluster, guilds: number, members: number) => void) | undefined,\n 'CLUSTER_STOPPED': ((cluster: BridgeClientCluster) => void) | undefined,\n 'CLUSTER_SPAWNED': ((cluster: BridgeClientCluster, connection: BridgeClientConnection) => void) | undefined,\n 'CLUSTER_RECLUSTER': ((cluster: BridgeClientCluster, newConnection: BridgeClientConnection, oldConnection: BridgeClientConnection) => void) | undefined,\n 'CLUSTER_HEARTBEAT_FAILED': ((cluster: BridgeClientCluster, error: unknown) => void) | undefined,\n 'CLIENT_CONNECTED': ((client: BridgeClientConnection) => void) | undefined,\n 'CLIENT_DISCONNECTED': ((client: BridgeClientConnection, reason: string) => void) | undefined,\n 'ERROR': ((error: string) => void) | undefined,\n 'CLIENT_STOP': ((instance: BridgeClientConnection) => void) | undefined\n};","import {BridgeClientCluster, BridgeClientClusterConnectionStatus} from \"./BridgeClientCluster\";\nimport {BridgeClientConnection, BridgeClientConnectionStatus} from \"./BridgeClientConnection\";\n\n/**\n * Manages the calculation and distribution of clusters for a Discord bot sharding system.\n * This class is responsible for creating clusters with their assigned shards,\n * tracking which clusters are in use, and providing methods to retrieve available clusters.\n */\nexport class ClusterCalculator {\n /** The total number of clusters to initialize */\n private readonly clusterToStart: number;\n\n /** The number of shards that each cluster will manage */\n private readonly shardsPerCluster: number;\n\n /** List of all clusters managed by this calculator */\n public readonly clusterList: BridgeClientCluster[]= [];\n\n /**\n * Creates a new ClusterCalculator and initializes the clusters.\n * \n * @param clusterToStart - The number of clusters to create\n * @param shardsPerCluster - The number of shards each cluster will manage\n */\n constructor(clusterToStart: number, shardsPerCluster: number) {\n this.shardsPerCluster = shardsPerCluster;\n this.clusterToStart = clusterToStart;\n\n this.calculateClusters();\n }\n\n /**\n * Calculates and initializes all clusters with their assigned shards.\n * Each cluster is assigned a sequential range of shard IDs based on its cluster index.\n */\n private calculateClusters(): void {\n const clusters: Map = new Map();\n for (let i = 0; i < this.clusterToStart; i++) {\n clusters.set(i, []);\n for (let j = 0; j < this.shardsPerCluster; j++) {\n clusters.get(i)?.push(i * this.shardsPerCluster + j);\n }\n }\n\n for (let [clusterIndex, clusterShards] of clusters.entries()) {\n this.clusterList.push(new BridgeClientCluster(clusterIndex, clusterShards));\n }\n }\n\n /**\n * Retrieves the next available (unused) cluster and marks it as used.\n * \n * @returns The next available cluster, or undefined if all clusters are in use\n */\n public getNextCluster(): BridgeClientCluster | undefined {\n for (const cluster of this.clusterList) {\n if (!cluster.isUsed()) {\n return cluster;\n }\n }\n return undefined; // No available clusters\n }\n\n /**\n * Retrieves multiple available clusters up to the specified count.\n * Each returned cluster is marked as used.\n * \n * @param count - The maximum number of clusters to retrieve\n * @returns An array of available clusters (may be fewer than requested if not enough are available)\n */\n public getNextClusters(count: number): BridgeClientCluster[] {\n const availableClusters: BridgeClientCluster[] = [];\n for (const cluster of this.clusterList) {\n if (!cluster.isUsed() && availableClusters.length < count) {\n availableClusters.push(cluster);\n }\n }\n return availableClusters; // Returns the clusters that were found\n }\n\n /**\n * Sets the used status of a specific cluster by its ID.\n *\n * @param clusterID - The ID of the cluster to update\n * @param connection - The connection to associate with the cluster\n */\n public clearClusterConnection(clusterID: number): void {\n const cluster = this.clusterList.find(c => c.clusterID === clusterID);\n if (cluster) {\n cluster.setConnection(undefined);\n }\n }\n\n public getClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[] {\n return this.clusterList.filter(cluster =>\n cluster.connection?.instanceID === connection.instanceID\n );\n }\n\n public getOldClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[] {\n return this.clusterList.filter(cluster =>\n cluster.oldConnection?.instanceID === connection.instanceID\n );\n }\n\n public checkAllClustersConnected(): boolean {\n for (const cluster of this.clusterList) {\n if (cluster.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED){\n return false; // At least one cluster is not in use\n }\n }\n return true; // All clusters are in use\n }\n\n\n findMostAndLeastClustersForConnections(\n connectedClients: BridgeClientConnection[]\n ): {\n most: BridgeClientConnection | undefined,\n least: BridgeClientConnection | undefined\n } {\n\n const openClients = connectedClients.filter(x => !x.dev)\n\n const devClients = connectedClients.filter(x => x.dev)\n const summDevConnectedClusters = devClients.map(c => this.getClusterForConnection(c).length).reduce((a, b) => a + b, 0);\n\n let most: BridgeClientConnection | undefined;\n let least: BridgeClientConnection | undefined;\n let remainder = ((this.clusterToStart - summDevConnectedClusters) % openClients.length || 0);\n\n for (const client of openClients) {\n const clusters = this.getClusterForConnection(client);\n\n if (!most || clusters.length > this.getClusterForConnection(most).length) {\n most = client;\n }\n\n if (!least || clusters.length < this.getClusterForConnection(least).length) {\n least = client;\n }\n }\n\n if (most && least) {\n const mostCount = this.getClusterForConnection(most).length;\n const leastCount = this.getClusterForConnection(least).length;\n\n // Only recluster if the difference is greater than remainder\n if (mostCount - leastCount <= remainder) {\n return {most: undefined, least: undefined};\n }\n }\n\n return {most, least};\n }\n\n getClusterWithLowestLoad(connectedClients: Map): BridgeClientConnection | undefined {\n let lowestLoadClient: BridgeClientConnection | undefined;\n let lowestLoad = Infinity;\n\n for (const client of connectedClients.values().filter(c =>\n c.connectionStatus === BridgeClientConnectionStatus.READY && !c.dev)) {\n const clusters = this.getClusterForConnection(client);\n\n const load = clusters.length; // Assuming load is determined by the number of clusters assigned\n if (load < lowestLoad) {\n lowestLoad = load;\n lowestLoadClient = client;\n }\n }\n\n return lowestLoadClient; // Returns the client with the lowest load, or undefined if no clients are connected\n }\n\n getClusterOfShard(shardID: number) {\n return this.clusterList.find(c => c.shardList.includes(shardID));\n }\n}\n","export class ShardingUtil {\n public static getShardIDForGuild(guildID: string, totalShards: number): number {\n if (!guildID || totalShards <= 0) {\n throw new Error(\"Invalid guild ID or total shards\");\n }\n\n return Number(BigInt(guildID) >> 22n) % totalShards;\n }\n}","import {Client, GatewayIntentsString, Status} from \"discord.js\";\nimport {EventManager} from \"../general/EventManager\";\nimport os from \"os\";\nexport class Cluster {\n\n public readonly instanceID: number;\n\n public readonly clusterID: number;\n\n public readonly shardList: number[] = [];\n\n public readonly totalShards: number;\n\n public readonly token: string;\n\n public readonly intents: GatewayIntentsString[];\n\n public eventManager: EventManager;\n\n public client!: T;\n\n public onSelfDestruct?: () => void;\n\n private readonly eventMap: {\n 'message': ((message: unknown) => void) | undefined,\n 'request': ((message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined,\n 'CLUSTER_READY': (() => void) | undefined,\n } = {\n message: undefined, request: undefined, CLUSTER_READY: undefined,\n }\n\n constructor(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]) {\n this.instanceID = instanceID;\n this.clusterID = clusterID;\n this.shardList = shardList;\n this.totalShards = totalShards;\n this.token = token;\n this.intents = intents;\n this.eventManager = new EventManager((message: unknown) => {\n return new Promise((resolve, reject) => {\n if (typeof process.send !== 'function') {\n reject(new Error(\"Process does not support sending messages\"));\n return;\n }\n\n process.send?.(message, undefined, undefined, (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }, (message: unknown) => {\n this._onMessage(message);\n }, (message: unknown) => {\n return this._onRequest(message);\n });\n process.on(\"message\", (message) => {\n this.eventManager.receive(message);\n })\n }\n\n static initial(): Cluster {\n const args = process.env;\n\n if (args.SHARD_LIST == undefined || args.INSTANCE_ID == undefined || args.TOTAL_SHARDS == undefined || args.TOKEN == undefined || args.INTENTS == undefined || args.CLUSTER_ID == undefined) {\n throw new Error(\"Missing required environment variables\");\n }\n\n const shardList = args.SHARD_LIST.split(',').map(Number);\n\n const totalShards = Number(args.TOTAL_SHARDS);\n\n const instanceID = Number(args.INSTANCE_ID);\n const clusterID = Number(args.CLUSTER_ID);\n\n const token = args.TOKEN;\n\n const intents = args.INTENTS.split(',').map(i => i.trim()) as GatewayIntentsString[];\n\n return new Cluster(instanceID, clusterID, shardList, totalShards, token, intents);\n }\n\n triggerReady(guilds: number, members: number) {\n this.eventManager.send({\n type: 'CLUSTER_READY',\n id: this.clusterID,\n guilds: guilds,\n members: members,\n });\n\n if(this.eventMap?.CLUSTER_READY) {\n this.eventMap?.CLUSTER_READY();\n }\n }\n\n triggerError(e: any) {\n this.eventManager.send({\n type: 'CLUSTER_ERROR',\n id: this.clusterID,\n });\n }\n\n private async wait(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n private _onMessage(message: unknown): void {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CUSTOM' && this.eventMap.message) {\n this.eventMap.message!(m.data);\n }\n }\n\n private _onRequest(message: unknown): unknown {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(m.data, resolve, reject);\n });\n } else if(m.type == 'CLUSTER_HEARTBEAT'){\n const startTime = process.hrtime.bigint();\n const startUsage = process.cpuUsage();\n\n (async () => {\n await this.wait(500);\n })();\n\n const endTime = process.hrtime.bigint();\n const usageDiff = process.cpuUsage(startUsage);\n\n const elapsedTimeUs = Number((endTime - startTime) / 1000n);\n const totalCPUTime = usageDiff.user + usageDiff.system;\n\n const cpuCount = os.cpus().length;\n const cpuPercent = (totalCPUTime / (elapsedTimeUs * cpuCount)) * 100;\n\n // Collect per-shard ping information in addition to the overall ws ping\n let shardPings: { id: number, ping: number, status: Status, uptime?: unknown, guilds: number, members: number }[] = [];\n try {\n const shards = this.client.ws.shards;\n\n if(shards) {\n shards.forEach((shard) => {\n shardPings.push({ id: shard.id, ping: shard.ping, status: shard.status,\n guilds: this.client.guilds.cache.filter(g => g.shardId === shard.id).size,\n members: this.client.guilds.cache.filter(g => g.shardId === shard.id).reduce((acc, g) => acc + g.memberCount, 0)\n });\n\n this.client.shard?.fetchClientValues('uptime', shard.id).then(values => {\n shardPings[shard.id][\"uptime\"] = values\n console.log(values)\n }).catch(e => {\n\n })\n })\n }\n } catch (_) {\n // ignore and keep empty shardPings on failure\n }\n\n return {\n cpu: { raw: process.cpuUsage(), cpuPercent: cpuPercent.toFixed(2) },\n memory: { raw: process.memoryUsage(),\n memoryPercent: ((process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100).toFixed(2) + '%',\n usage: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + 'MB'\n },\n ping: this.client.ws.ping,\n shardPings: shardPings,\n }\n } else if(m.type == 'BROADCAST_EVAL'){\n const broadcast = message as { type: 'BROADCAST_EVAL', data: string }\n\n const fn = eval(`(${broadcast.data})`);\n\n const result = fn(this.client);\n if(result instanceof Promise){\n return new Promise((resolve, reject) => {\n result.then(res => {\n resolve(res);\n }).catch(err => {\n reject(err);\n });\n });\n } else {\n return result;\n }\n } else if(m.type == 'SELF_DESTRUCT') {\n if(this.onSelfDestruct) {\n this.onSelfDestruct();\n }\n }\n return undefined;\n }\n\n public on(event: K, listener: ClusterEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public sendMessage(data: unknown) {\n this.eventManager.send({\n type: 'CUSTOM',\n data: data,\n });\n }\n\n public sendRequest(data: unknown, timeout = 5000): Promise {\n return this.eventManager.request({\n type: 'CUSTOM',\n data: data,\n }, timeout);\n }\n\n public broadcastEval(fn: (cluster: T) => Result, timeout = 20000): Promise {\n return this.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: fn.toString(),\n }, timeout);\n }\n\n\n public sendMessageToClusterOfGuild(guildID: string, message: unknown): void {\n if (this.eventManager) {\n this.eventManager.send({\n type: 'REDIRECT_MESSAGE_TO_GUILD',\n guildID: guildID,\n data: message\n });\n }\n }\n\n public sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n if (this.eventManager) {\n this.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n guildID: guildID,\n data: message\n }, timeout).then((response) => {\n resolve(response);\n }).catch((error) => {\n reject(error);\n });\n } else {\n reject(new Error(\"Event manager is not initialized\"));\n }\n });\n }\n}\n\nexport type ClusterEventListeners = {\n message: (message: unknown) => void;\n request: (message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void;\n\n CLUSTER_READY: () => void;\n};","import {ChildProcess} from \"child_process\";\nimport {EventManager} from \"../general/EventManager\";\n\nexport type ClusterProcessState = 'starting' | 'running' | 'stopped';\n\nexport class ClusterProcess {\n public readonly child: ChildProcess;\n public readonly eventManager: EventManager;\n public readonly id: number;\n public readonly shardList: number[];\n public readonly totalShards: number;\n public status: ClusterProcessState;\n public readonly createdAt: number = Date.now();\n\n private _onMessage?: (message: unknown) => void;\n private _onRequest?: (message: unknown) => unknown;\n\n constructor(id: number, child: ChildProcess, shardList: number[], totalShards: number) {\n this.id = id;\n this.child = child;\n this.shardList = shardList;\n this.totalShards = totalShards;\n this.status = 'starting';\n this.eventManager = new EventManager((message) => {\n return new Promise((resolve, reject) => {\n this.child.send(message, (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n })\n }, (message) => {\n if (this._onMessage) {\n this._onMessage(message);\n }\n }, (message) => {\n if (this._onRequest) {\n return this._onRequest(message);\n }\n return undefined;\n })\n\n this.child.on('message', (message) => {\n this.eventManager.receive(message);\n });\n\n // Ensure we do not retain pending requests if the child dies or errors\n this.child.on('exit', () => {\n this.eventManager.close('child process exited');\n });\n this.child.on('error', () => {\n this.eventManager.close('child process error');\n });\n }\n\n onMessage(callback: (message: unknown) => void) {\n this._onMessage = callback;\n }\n\n onRequest(callback: (message: unknown) => unknown) {\n this._onRequest = callback;\n }\n\n public sendMessage(data: unknown) {\n this.eventManager.send({\n type: 'CUSTOM',\n data: data,\n });\n }\n\n public sendRequest(data: unknown, timeout = 5000): Promise {\n return this.eventManager.request({\n type: 'CUSTOM',\n data: data,\n }, timeout);\n }\n}","import {fork} from 'child_process';\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport abstract class BotInstance {\n\n private readonly entryPoint: string;\n\n private readonly execArgv: string[];\n\n public readonly clients: Map = new Map();\n\n protected constructor(entryPoint: string, execArgv?: string[]) {\n this.entryPoint = entryPoint;\n this.execArgv = execArgv ?? [];\n }\n\n protected readonly eventMap: BotInstanceEventListeners = {\n 'message': undefined,\n 'request': undefined,\n\n 'PROCESS_KILLED': undefined,\n 'PROCESS_SELF_DESTRUCT_ERROR': undefined,\n 'PROCESS_SPAWNED': undefined,\n 'ERROR': undefined,\n 'PROCESS_ERROR': undefined,\n 'CLUSTER_READY': undefined,\n 'CLUSTER_ERROR': undefined,\n 'CLUSTER_RECLUSTER': undefined,\n 'BRIDGE_CONNECTION_ESTABLISHED': undefined,\n 'BRIDGE_CONNECTION_CLOSED': undefined,\n 'BRIDGE_CONNECTION_STATUS_CHANGE': undefined,\n 'INSTANCE_STOP': undefined,\n 'INSTANCE_STOPPED': undefined,\n 'SELF_CHECK_SUCCESS': undefined,\n 'SELF_CHECK_ERROR': undefined,\n 'SELF_CHECK_RECEIVED': undefined,\n }\n\n protected startProcess(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]): void {\n try {\n const child = fork(this.entryPoint, {\n env: {\n INSTANCE_ID: instanceID.toString(),\n CLUSTER_ID: clusterID.toString(),\n SHARD_LIST: shardList.join(','),\n TOTAL_SHARDS: totalShards.toString(),\n TOKEN: token,\n INTENTS: intents.join(','),\n FORCE_COLOR: 'true'\n },\n stdio: 'inherit',\n execArgv: this.execArgv,\n silent: false,\n detached: true,\n })\n\n const client = new ClusterProcess(clusterID, child, shardList, totalShards);\n\n child.stdout?.on('data', (data) => {\n process.stdout.write(data);\n });\n\n child.stderr?.on('data', (data) => {\n process.stderr.write(data);\n });\n\n child.on(\"spawn\", () => {\n if(this.eventMap.PROCESS_SPAWNED) this.eventMap.PROCESS_SPAWNED(client);\n\n this.setClusterSpawned(client);\n\n this.clients.set(clusterID, client);\n\n client.onMessage((message) => {\n this.onMessage(client, message);\n })\n\n client.onRequest((message) => {\n return this.onRequest(client, message);\n });\n });\n\n child.on(\"error\", (err) => {\n if(this.eventMap.PROCESS_ERROR) this.eventMap.PROCESS_ERROR(client, err);\n })\n\n child.on(\"exit\", (err: Error) => {\n if(client.status !== 'stopped') {\n client.status = 'stopped';\n this.killProcess(client, `Process exited: ${err?.message}`);\n }\n })\n } catch (error) {\n throw new Error(`Failed to start process for cluster ${clusterID}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n protected killProcess(client: ClusterProcess, reason: string): void {\n client.status = 'stopped';\n\n client.eventManager.request({\n type: 'SELF_DESTRUCT',\n reason: reason\n }, 5000).catch(() => {\n if(this.eventMap.PROCESS_SELF_DESTRUCT_ERROR) this.eventMap.PROCESS_SELF_DESTRUCT_ERROR(client, reason, 'Cluster didnt respond to shot-call.');\n }).finally(() => {\n if (client.child && client.child.pid) {\n if(client.child.kill(\"SIGKILL\")) {\n if(this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, true);\n } else {\n if(this.eventMap.ERROR) this.eventMap.ERROR(`Failed to kill process for cluster ${client.id}`);\n client.child.kill(\"SIGKILL\");\n }\n try { process.kill(-client.child.pid) } catch {}\n } else {\n if(this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, false);\n }\n this.clients.delete(client.id);\n this.setClusterStopped(client, reason);\n })\n }\n\n protected abstract setClusterStopped(client: ClusterProcess, reason: string): void;\n\n protected abstract setClusterReady(client: ClusterProcess, guilds: number, members: number): void;\n\n protected abstract setClusterSpawned(client: ClusterProcess): void;\n\n public abstract start(): void;\n\n private onMessage(client: ClusterProcess, message: any): void {\n if(message.type === 'CLUSTER_READY') {\n client.status = 'running';\n if(this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(client);\n this.setClusterReady(client, message.guilds || 0, message.members || 0);\n }\n\n if (message.type === 'CLUSTER_ERROR') {\n client.status = 'stopped';\n if(this.eventMap.CLUSTER_ERROR) this.eventMap.CLUSTER_ERROR(client, message.error);\n this.killProcess(client, 'Cluster error: ' + message.error);\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.message) {\n this.eventMap.message!(client, message.data);\n }\n }\n\n protected abstract onRequest(client: ClusterProcess, message: any): Promise;\n\n public on(event: K, listener: BotInstanceEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n for (const client of this.clients.values()) {\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if (client.shardList.includes(shardID)) {\n client.eventManager.request({\n type: 'CUSTOM',\n data: message\n }, timeout).then(resolve).catch(reject);\n return;\n }\n }\n reject(new Error(`No cluster found for guild ${guildID}`));\n });\n }\n\n public sendRequestToCluster(cluster: ClusterProcess, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n cluster.eventManager.request({\n type: 'CUSTOM',\n data: message\n }, timeout).then(resolve).catch(reject);\n return;\n });\n }\n}\n\nexport type BotInstanceEventListeners = {\n 'message': ((client: ClusterProcess,message: unknown) => void) | undefined,\n 'request': ((client: ClusterProcess, message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined,\n\n 'PROCESS_KILLED': ((client: ClusterProcess, reason: string, processKilled: boolean) => void) | undefined,\n 'PROCESS_SELF_DESTRUCT_ERROR': ((client: ClusterProcess, reason: string, error: unknown) => void) | undefined,\n 'PROCESS_SPAWNED': ((client: ClusterProcess) => void) | undefined,\n 'PROCESS_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined,\n 'CLUSTER_READY': ((client: ClusterProcess) => void) | undefined,\n 'CLUSTER_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined,\n 'CLUSTER_RECLUSTER': ((client: ClusterProcess) => void) | undefined,\n 'ERROR': ((error: string) => void) | undefined,\n\n 'BRIDGE_CONNECTION_ESTABLISHED': (() => void) | undefined,\n 'BRIDGE_CONNECTION_CLOSED': ((reason: string) => void) | undefined,\n 'BRIDGE_CONNECTION_STATUS_CHANGE': ((status: number) => void) | undefined,\n 'INSTANCE_STOP': (() => void) | undefined,\n 'INSTANCE_STOPPED': (() => void) | undefined,\n\n 'SELF_CHECK_SUCCESS': (() => void) | undefined,\n 'SELF_CHECK_ERROR': ((error: string) => void) | undefined,\n 'SELF_CHECK_RECEIVED': ((data: { clusterList: number[] }) => void) | undefined,\n};","import {BotInstance} from \"./BotInstance\";\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {Client} from \"net-ipc\";\nimport {EventManager} from \"../general/EventManager\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport enum BridgeConnectionStatus {\n CONNECTED,\n DISCONNECTED,\n}\n\nexport class ManagedInstance extends BotInstance {\n\n private readonly host: string;\n\n private readonly port: number;\n\n private readonly instanceID: number;\n\n private eventManager!: EventManager;\n\n private connectionStatus: BridgeConnectionStatus = BridgeConnectionStatus.DISCONNECTED;\n\n private data: unknown;\n\n private dev: boolean = false;\n\n constructor(entryPoint: string, host: string, port: number, instanceID: number, data: unknown, execArgv?: string[], dev?: boolean) {\n super(entryPoint, execArgv);\n\n this.host = host;\n this.port = port;\n this.instanceID = instanceID;\n this.data = data;\n this.dev = dev || false;\n }\n\n public start() {\n const client = new Client({\n host: this.host,\n port: this.port,\n reconnect: true,\n retries: 100\n });\n\n this.eventManager = new EventManager((message) => {\n if(client.status == 3) {\n return client.send(message);\n }\n return Promise.reject(new Error('Client is not ready to send messages'));\n }, (message) => {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CLUSTER_CREATE') {\n this.onClusterCreate(m.data)\n } else if (m.type == 'CLUSTER_STOP') {\n this.onClusterStop(m.data);\n } else if(m.type == 'CLUSTER_RECLUSTER') {\n this.onClusterRecluster(m.data);\n } else if(m.type == 'INSTANCE_STOP') {\n if(this.eventMap.INSTANCE_STOP) {\n this.eventMap.INSTANCE_STOP();\n }\n } else if(m.type == 'INSTANCE_STOPPED') {\n if(this.eventMap.INSTANCE_STOPPED) {\n this.eventMap.INSTANCE_STOPPED();\n }\n }\n }, (message) => {\n return this.onBridgeRequest(message);\n });\n\n setInterval(() => {\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.selfCheck();\n }\n }, 2500); // 5 minutes\n\n client.connect({\n id: this.instanceID,\n dev: this.dev,\n data: this.data,\n }).then(_ => {\n if(this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED();\n this.connectionStatus = BridgeConnectionStatus.CONNECTED;\n\n client.on(\"message\", (message) => {\n this.eventManager?.receive(message);\n })\n client.on(\"close\", (reason) => {\n if(this.eventMap.BRIDGE_CONNECTION_CLOSED) this.eventMap.BRIDGE_CONNECTION_CLOSED(reason);\n\n // kill all\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.clients.forEach((client) => {\n this.killProcess(client, 'Bridge connection closed');\n });\n }\n this.connectionStatus = BridgeConnectionStatus.DISCONNECTED;\n });\n\n client.on(\"status\", (status) => {\n if(this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE) this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE(status);\n\n if(status == 4){\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.clients.forEach((client) => {\n this.killProcess(client, 'Bridge connection closed');\n });\n }\n this.connectionStatus = BridgeConnectionStatus.DISCONNECTED;\n } else if(status == 3){\n this.connectionStatus = BridgeConnectionStatus.CONNECTED;\n if(this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED();\n }\n });\n })\n }\n\n private selfCheck() {\n this.eventManager.request({\n type: 'SELF_CHECK'\n }, 1000 * 60).then((r) => {\n const response = r as { clusterList: number[] };\n\n if(this.eventMap.SELF_CHECK_RECEIVED) {\n this.eventMap.SELF_CHECK_RECEIVED(response);\n }\n\n const startingClusters = this.clients.values().filter(c => c.status == 'starting').toArray();\n startingClusters.forEach((c: ClusterProcess) => {\n if (Date.now() - c.createdAt > 10 * 60 * 1000) {\n this.killProcess(c, 'Cluster took too long to start');\n }\n })\n\n // check if there is an wrong cluster on this host\n const wrongClusters = this.clients.values().filter(c => !response.clusterList.includes(c.id)).toArray();\n if(wrongClusters.length > 0) {\n if(this.eventMap.SELF_CHECK_ERROR) {\n this.eventMap.SELF_CHECK_ERROR(`Self check found wrong clusters: ${wrongClusters.map(c => c.id).join(', ')}`);\n }\n wrongClusters.forEach(c => {\n this.killProcess(c, 'Self check found wrong cluster');\n });\n } else {\n if(this.eventMap.SELF_CHECK_SUCCESS) {\n this.eventMap.SELF_CHECK_SUCCESS();\n }\n }\n }).catch((err) => {\n if(this.eventMap.SELF_CHECK_ERROR) {\n this.eventMap.SELF_CHECK_ERROR(`Self check failed: ${err}`);\n }\n });\n }\n\n protected setClusterStopped(client: ClusterProcess, reason: string): void {\n this.eventManager?.send({\n type: 'CLUSTER_STOPPED',\n data: {\n id: client.id,\n reason: reason\n }\n }).catch(() => {\n return null\n });\n }\n\n protected setClusterReady(client: ClusterProcess, guilds: number, members: number): void {\n this.eventManager?.send({\n type: 'CLUSTER_READY',\n data: {\n id: client.id,\n guilds: guilds,\n members: members\n }\n });\n }\n\n protected setClusterSpawned(client: ClusterProcess): void {\n this.eventManager?.send({\n type: 'CLUSTER_SPAWNED',\n data: {\n id: client.id\n }\n });\n }\n\n private onClusterCreate(message: unknown){\n const m = message as { clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[] }\n\n if(this.clients.has(m.clusterID)) {\n this.eventManager?.send({\n type: 'CLUSTER_STOPPED',\n data: {\n id: m.clusterID,\n reason: 'Cluster already exists'\n }\n }).catch(() => {\n return null\n });\n return;\n }\n\n this.startProcess(this.instanceID, m.clusterID, m.shardList, m.totalShards, m.token, m.intents);\n }\n\n private onClusterStop(message: unknown) {\n const m = message as { id: number };\n const cluster = this.clients.get(m.id)\n if (cluster) {\n this.killProcess(cluster, `Request to stop cluster ${m.id}`);\n }\n }\n\n private onClusterRecluster(message: unknown) {\n const m = message as { clusterID: number }\n const cluster = this.clients.get(m.clusterID);\n if (this.eventMap.CLUSTER_RECLUSTER && cluster) {\n this.eventMap.CLUSTER_RECLUSTER(cluster);\n }\n }\n\n protected onRequest(client: ClusterProcess, message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = message.guildID;\n const data = message.data;\n\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if(client.shardList.includes(shardID)) {\n return client.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return this.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n guildID: guildID,\n data: data\n }, 5000)\n }\n }\n\n if(message.type == 'BROADCAST_EVAL') {\n return this.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data\n }, 5000)\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(client, message.data, resolve, reject);\n });\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n private onBridgeRequest(message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const clusterID = message.clusterID;\n const data = message.data;\n\n const cluster = this.clients.get(clusterID);\n if(cluster){\n return cluster.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`));\n }\n } else if(message.type == 'CLUSTER_HEARTBEAT'){\n const clusterID = message.data.clusterID;\n const cluster = this.clients.get(clusterID);\n if (cluster) {\n return new Promise((resolve, reject) => {\n cluster.eventManager.request({\n type: 'CLUSTER_HEARTBEAT'\n }, 15000).then((r) => {\n resolve(r);\n }).catch((err) => {\n reject(err);\n });\n })\n } else {\n return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`));\n }\n } else if(message.type == 'BROADCAST_EVAL') {\n return Promise.all(this.clients.values().filter(c => c.status == 'running').map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data,\n }, 5000);\n }));\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n stopInstance(): void {\n this.eventManager?.send({\n type: 'INSTANCE_STOP'\n });\n }\n\n}","import {BotInstance} from \"./BotInstance\";\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport class StandaloneInstance extends BotInstance {\n private readonly totalClusters: number;\n private readonly shardsPerCluster: number;\n\n public readonly token: string;\n public readonly intents: GatewayIntentsString[];\n\n constructor(entryPoint: string, shardsPerCluster: number, totalClusters: number, token: string, intents: GatewayIntentsString[], execArgv?: string[]) {\n super(entryPoint, execArgv);\n this.shardsPerCluster = shardsPerCluster;\n this.totalClusters = totalClusters;\n this.token = token;\n this.intents = intents;\n }\n\n get totalShards(): number {\n return this.shardsPerCluster * this.totalClusters;\n }\n\n private calculateClusters(): Record {\n const clusters: Record = {};\n for (let i = 0; i < this.totalClusters; i++) {\n clusters[i] = [];\n for (let j = 0; j < this.shardsPerCluster; j++) {\n clusters[i].push(i * this.shardsPerCluster + j);\n }\n }\n return clusters;\n }\n\n public start(): void {\n const clusters = this.calculateClusters();\n for (const [id, shardList] of Object.entries(clusters)) {\n this.startProcess(1, Number(id), shardList, this.totalShards, this.token, this.intents);\n }\n }\n\n protected setClusterStopped(client: ClusterProcess, reason: string): void {\n this.clients.delete(client.id);\n this.restartProcess(client);\n }\n\n protected setClusterReady(client: ClusterProcess): void {\n \n }\n\n protected setClusterSpawned(client: ClusterProcess): void {\n\n }\n\n private restartProcess(client: ClusterProcess): void {\n this.startProcess(1, client.id, client.shardList, this.totalShards, this.token, this.intents);\n }\n\n protected onRequest(client: ClusterProcess, message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = message.guildID;\n const data = message.data;\n\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if(client.shardList.includes(shardID)) {\n return client.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return Promise.reject(new Error(`Shard ID ${shardID} not found in cluster ${client.id} for guild ${guildID}`));\n }\n }\n\n if(message.type == 'BROADCAST_EVAL') {\n return Promise.all(\n this.clients.values().map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data,\n }, 5000);\n })\n );\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(client, message.data, resolve, reject);\n });\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n}"],"mappings":"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","names":["BridgeClientClusterConnectionStatus","result","BridgeClientConnectionStatus","message","m","message","message","m","fn","message","message","BridgeConnectionStatus","message","m","client","message"]} \ No newline at end of file From 9e1543601cbdb650f6005a37601596405b7619f6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 00:40:12 +0000 Subject: [PATCH 3/3] Rename CLIENT to HOST in Bridge events Co-authored-by: Nevercold <32977558+Nevercold@users.noreply.github.com> --- .gitignore | 1 + dist/index.d.mts | 332 --------- dist/index.d.ts | 332 --------- dist/index.js | 1576 ------------------------------------------ dist/index.js.map | 1 - dist/index.mjs | 1526 ---------------------------------------- dist/index.mjs.map | 1 - src/bridge/Bridge.ts | 16 +- 8 files changed, 9 insertions(+), 3776 deletions(-) delete mode 100644 dist/index.d.mts delete mode 100644 dist/index.d.ts delete mode 100644 dist/index.js delete mode 100644 dist/index.js.map delete mode 100644 dist/index.mjs delete mode 100644 dist/index.mjs.map diff --git a/.gitignore b/.gitignore index 2ab7b70..8e42fc8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /tmp /out-tsc +/dist /node_modules npm-debug.log* diff --git a/dist/index.d.mts b/dist/index.d.mts deleted file mode 100644 index c781ae2..0000000 --- a/dist/index.d.mts +++ /dev/null @@ -1,332 +0,0 @@ -import { Connection, Server } from 'net-ipc'; -import { GatewayIntentsString, Snowflake, Client } from 'discord.js'; -import { ChildProcess } from 'child_process'; - -type EventPayload = { - id: string; - type: 'message' | 'request' | 'response' | 'response_error'; - data: unknown; -}; - -declare class EventManager { - private pendingPayloads; - private pendingTimeouts; - private readonly _send; - private readonly _on; - private readonly _request; - constructor(send: (payload: EventPayload) => Promise, on: (message: unknown) => void, request: (message: unknown) => unknown); - send(data: unknown): Promise; - request(payload: unknown, timeout: number): Promise; - receive(possiblePayload: unknown): void; - close(reason?: string): void; -} - -declare enum BridgeClientConnectionStatus { - READY = "ready", - PENDING_STOP = "pending_stop" -} -declare class BridgeClientConnection { - readonly instanceID: number; - readonly eventManager: EventManager; - readonly connection: Connection; - readonly data: unknown; - connectionStatus: BridgeClientConnectionStatus; - readonly dev: boolean; - readonly establishedAt: number; - private _onMessage?; - private _onRequest?; - constructor(instanceID: number, connection: Connection, data: unknown, dev: boolean); - messageReceive(message: any): void; - onRequest(callback: (message: unknown) => unknown): void; - onMessage(callback: (message: unknown) => void): void; -} - -declare enum BridgeClientClusterConnectionStatus { - REQUESTING = "requesting", - STARTING = "starting", - CONNECTED = "connected", - RECLUSTERING = "reclustering", - DISCONNECTED = "disconnected" -} -declare class BridgeClientCluster { - readonly clusterID: number; - readonly shardList: number[]; - connectionStatus: BridgeClientClusterConnectionStatus; - connection?: BridgeClientConnection; - oldConnection?: BridgeClientConnection; - missedHeartbeats: number; - heartbeatResponse?: HeartbeatResponse; - heartbeatPending: boolean; - startedAt?: number; - constructor(clusterID: number, shardList: number[]); - setConnection(connection?: BridgeClientConnection): void; - setOldConnection(connection?: BridgeClientConnection): void; - isUsed(): boolean; - reclustering(connection: BridgeClientConnection): void; - addMissedHeartbeat(): void; - removeMissedHeartbeat(): void; - resetMissedHeartbeats(): void; -} -type HeartbeatResponse = { - cpu: { - raw: { - user: number; - system: number; - }; - cpuPercent: string; - }; - memory: { - raw: { - rss: number; - heapTotal: number; - heapUsed: number; - external: number; - arrayBuffers: number; - }; - memoryPercent: string; - usage: number; - }; - ping: number; - shardPings: { - id: number; - ping: number; - status: number; - guilds: number; - members: number; - }[]; -}; - -declare class Bridge { - readonly port: number; - readonly server: Server; - readonly connectedClients: Map; - private readonly token; - private readonly intents; - private readonly shardsPerCluster; - private readonly clusterToStart; - private readonly reclusteringTimeoutInMs; - private readonly clusterCalculator; - private readonly eventMap; - constructor(port: number, token: string, intents: GatewayIntentsString[], shardsPerCluster: number, clusterToStart: number, reclusteringTimeoutInMs: number); - start(): void; - private interval; - private checkRecluster; - private heartbeat; - private checkCreate; - private createCluster; - startListening(): void; - sendMessageToClient(clientId: string, message: unknown): void; - private getTotalShards; - on(event: K, listener: BridgeEventListeners[K]): void; - getClusters(): BridgeClientCluster[]; - stopAllInstances(): Promise; - stopAllInstancesWithRestart(): Promise; - moveCluster(instance: BridgeClientConnection, cluster: BridgeClientCluster): Promise; - stopInstance(instance: BridgeClientConnection, recluster?: boolean): Promise; - sendRequestToGuild(cluster: BridgeClientCluster, guildID: Snowflake, data: unknown, timeout?: number): Promise; -} -type BridgeEventListeners = { - 'CLUSTER_READY': ((cluster: BridgeClientCluster, guilds: number, members: number) => void) | undefined; - 'CLUSTER_STOPPED': ((cluster: BridgeClientCluster) => void) | undefined; - 'CLUSTER_SPAWNED': ((cluster: BridgeClientCluster, connection: BridgeClientConnection) => void) | undefined; - 'CLUSTER_RECLUSTER': ((cluster: BridgeClientCluster, newConnection: BridgeClientConnection, oldConnection: BridgeClientConnection) => void) | undefined; - 'CLUSTER_HEARTBEAT_FAILED': ((cluster: BridgeClientCluster, error: unknown) => void) | undefined; - 'CLIENT_CONNECTED': ((client: BridgeClientConnection) => void) | undefined; - 'CLIENT_DISCONNECTED': ((client: BridgeClientConnection, reason: string) => void) | undefined; - 'ERROR': ((error: string) => void) | undefined; - 'CLIENT_STOP': ((instance: BridgeClientConnection) => void) | undefined; -}; - -/** - * Manages the calculation and distribution of clusters for a Discord bot sharding system. - * This class is responsible for creating clusters with their assigned shards, - * tracking which clusters are in use, and providing methods to retrieve available clusters. - */ -declare class ClusterCalculator { - /** The total number of clusters to initialize */ - private readonly clusterToStart; - /** The number of shards that each cluster will manage */ - private readonly shardsPerCluster; - /** List of all clusters managed by this calculator */ - readonly clusterList: BridgeClientCluster[]; - /** - * Creates a new ClusterCalculator and initializes the clusters. - * - * @param clusterToStart - The number of clusters to create - * @param shardsPerCluster - The number of shards each cluster will manage - */ - constructor(clusterToStart: number, shardsPerCluster: number); - /** - * Calculates and initializes all clusters with their assigned shards. - * Each cluster is assigned a sequential range of shard IDs based on its cluster index. - */ - private calculateClusters; - /** - * Retrieves the next available (unused) cluster and marks it as used. - * - * @returns The next available cluster, or undefined if all clusters are in use - */ - getNextCluster(): BridgeClientCluster | undefined; - /** - * Retrieves multiple available clusters up to the specified count. - * Each returned cluster is marked as used. - * - * @param count - The maximum number of clusters to retrieve - * @returns An array of available clusters (may be fewer than requested if not enough are available) - */ - getNextClusters(count: number): BridgeClientCluster[]; - /** - * Sets the used status of a specific cluster by its ID. - * - * @param clusterID - The ID of the cluster to update - * @param connection - The connection to associate with the cluster - */ - clearClusterConnection(clusterID: number): void; - getClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[]; - getOldClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[]; - checkAllClustersConnected(): boolean; - findMostAndLeastClustersForConnections(connectedClients: BridgeClientConnection[]): { - most: BridgeClientConnection | undefined; - least: BridgeClientConnection | undefined; - }; - getClusterWithLowestLoad(connectedClients: Map): BridgeClientConnection | undefined; - getClusterOfShard(shardID: number): BridgeClientCluster | undefined; -} - -declare class Cluster { - readonly instanceID: number; - readonly clusterID: number; - readonly shardList: number[]; - readonly totalShards: number; - readonly token: string; - readonly intents: GatewayIntentsString[]; - eventManager: EventManager; - client: T; - onSelfDestruct?: () => void; - private readonly eventMap; - constructor(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]); - static initial(): Cluster; - triggerReady(guilds: number, members: number): void; - triggerError(e: any): void; - private wait; - private _onMessage; - private _onRequest; - on(event: K, listener: ClusterEventListeners[K]): void; - sendMessage(data: unknown): void; - sendRequest(data: unknown, timeout?: number): Promise; - broadcastEval(fn: (cluster: T) => Result, timeout?: number): Promise; - sendMessageToClusterOfGuild(guildID: string, message: unknown): void; - sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout?: number): Promise; -} -type ClusterEventListeners = { - message: (message: unknown) => void; - request: (message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void; - CLUSTER_READY: () => void; -}; - -type ClusterProcessState = 'starting' | 'running' | 'stopped'; -declare class ClusterProcess { - readonly child: ChildProcess; - readonly eventManager: EventManager; - readonly id: number; - readonly shardList: number[]; - readonly totalShards: number; - status: ClusterProcessState; - readonly createdAt: number; - private _onMessage?; - private _onRequest?; - constructor(id: number, child: ChildProcess, shardList: number[], totalShards: number); - onMessage(callback: (message: unknown) => void): void; - onRequest(callback: (message: unknown) => unknown): void; - sendMessage(data: unknown): void; - sendRequest(data: unknown, timeout?: number): Promise; -} - -declare class ShardingUtil { - static getShardIDForGuild(guildID: string, totalShards: number): number; -} - -declare abstract class BotInstance { - private readonly entryPoint; - private readonly execArgv; - readonly clients: Map; - protected constructor(entryPoint: string, execArgv?: string[]); - protected readonly eventMap: BotInstanceEventListeners; - protected startProcess(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]): void; - protected killProcess(client: ClusterProcess, reason: string): void; - protected abstract setClusterStopped(client: ClusterProcess, reason: string): void; - protected abstract setClusterReady(client: ClusterProcess, guilds: number, members: number): void; - protected abstract setClusterSpawned(client: ClusterProcess): void; - abstract start(): void; - private onMessage; - protected abstract onRequest(client: ClusterProcess, message: any): Promise; - on(event: K, listener: BotInstanceEventListeners[K]): void; - sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout?: number): Promise; - sendRequestToCluster(cluster: ClusterProcess, message: unknown, timeout?: number): Promise; -} -type BotInstanceEventListeners = { - 'message': ((client: ClusterProcess, message: unknown) => void) | undefined; - 'request': ((client: ClusterProcess, message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined; - 'PROCESS_KILLED': ((client: ClusterProcess, reason: string, processKilled: boolean) => void) | undefined; - 'PROCESS_SELF_DESTRUCT_ERROR': ((client: ClusterProcess, reason: string, error: unknown) => void) | undefined; - 'PROCESS_SPAWNED': ((client: ClusterProcess) => void) | undefined; - 'PROCESS_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined; - 'CLUSTER_READY': ((client: ClusterProcess) => void) | undefined; - 'CLUSTER_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined; - 'CLUSTER_RECLUSTER': ((client: ClusterProcess) => void) | undefined; - 'ERROR': ((error: string) => void) | undefined; - 'BRIDGE_CONNECTION_ESTABLISHED': (() => void) | undefined; - 'BRIDGE_CONNECTION_CLOSED': ((reason: string) => void) | undefined; - 'BRIDGE_CONNECTION_STATUS_CHANGE': ((status: number) => void) | undefined; - 'INSTANCE_STOP': (() => void) | undefined; - 'INSTANCE_STOPPED': (() => void) | undefined; - 'SELF_CHECK_SUCCESS': (() => void) | undefined; - 'SELF_CHECK_ERROR': ((error: string) => void) | undefined; - 'SELF_CHECK_RECEIVED': ((data: { - clusterList: number[]; - }) => void) | undefined; -}; - -declare enum BridgeConnectionStatus { - CONNECTED = 0, - DISCONNECTED = 1 -} -declare class ManagedInstance extends BotInstance { - private readonly host; - private readonly port; - private readonly instanceID; - private eventManager; - private connectionStatus; - private data; - private dev; - constructor(entryPoint: string, host: string, port: number, instanceID: number, data: unknown, execArgv?: string[], dev?: boolean); - start(): void; - private selfCheck; - protected setClusterStopped(client: ClusterProcess, reason: string): void; - protected setClusterReady(client: ClusterProcess, guilds: number, members: number): void; - protected setClusterSpawned(client: ClusterProcess): void; - private onClusterCreate; - private onClusterStop; - private onClusterRecluster; - protected onRequest(client: ClusterProcess, message: any): Promise; - private onBridgeRequest; - stopInstance(): void; -} - -declare class StandaloneInstance extends BotInstance { - private readonly totalClusters; - private readonly shardsPerCluster; - readonly token: string; - readonly intents: GatewayIntentsString[]; - constructor(entryPoint: string, shardsPerCluster: number, totalClusters: number, token: string, intents: GatewayIntentsString[], execArgv?: string[]); - get totalShards(): number; - private calculateClusters; - start(): void; - protected setClusterStopped(client: ClusterProcess, reason: string): void; - protected setClusterReady(client: ClusterProcess): void; - protected setClusterSpawned(client: ClusterProcess): void; - private restartProcess; - protected onRequest(client: ClusterProcess, message: any): Promise; -} - -export { BotInstance, type BotInstanceEventListeners, Bridge, BridgeClientCluster, BridgeClientClusterConnectionStatus, BridgeClientConnection, BridgeClientConnectionStatus, BridgeConnectionStatus, type BridgeEventListeners, Cluster, ClusterCalculator, type ClusterEventListeners, ClusterProcess, type ClusterProcessState, EventManager, type EventPayload, type HeartbeatResponse, ManagedInstance, ShardingUtil, StandaloneInstance }; diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index c781ae2..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,332 +0,0 @@ -import { Connection, Server } from 'net-ipc'; -import { GatewayIntentsString, Snowflake, Client } from 'discord.js'; -import { ChildProcess } from 'child_process'; - -type EventPayload = { - id: string; - type: 'message' | 'request' | 'response' | 'response_error'; - data: unknown; -}; - -declare class EventManager { - private pendingPayloads; - private pendingTimeouts; - private readonly _send; - private readonly _on; - private readonly _request; - constructor(send: (payload: EventPayload) => Promise, on: (message: unknown) => void, request: (message: unknown) => unknown); - send(data: unknown): Promise; - request(payload: unknown, timeout: number): Promise; - receive(possiblePayload: unknown): void; - close(reason?: string): void; -} - -declare enum BridgeClientConnectionStatus { - READY = "ready", - PENDING_STOP = "pending_stop" -} -declare class BridgeClientConnection { - readonly instanceID: number; - readonly eventManager: EventManager; - readonly connection: Connection; - readonly data: unknown; - connectionStatus: BridgeClientConnectionStatus; - readonly dev: boolean; - readonly establishedAt: number; - private _onMessage?; - private _onRequest?; - constructor(instanceID: number, connection: Connection, data: unknown, dev: boolean); - messageReceive(message: any): void; - onRequest(callback: (message: unknown) => unknown): void; - onMessage(callback: (message: unknown) => void): void; -} - -declare enum BridgeClientClusterConnectionStatus { - REQUESTING = "requesting", - STARTING = "starting", - CONNECTED = "connected", - RECLUSTERING = "reclustering", - DISCONNECTED = "disconnected" -} -declare class BridgeClientCluster { - readonly clusterID: number; - readonly shardList: number[]; - connectionStatus: BridgeClientClusterConnectionStatus; - connection?: BridgeClientConnection; - oldConnection?: BridgeClientConnection; - missedHeartbeats: number; - heartbeatResponse?: HeartbeatResponse; - heartbeatPending: boolean; - startedAt?: number; - constructor(clusterID: number, shardList: number[]); - setConnection(connection?: BridgeClientConnection): void; - setOldConnection(connection?: BridgeClientConnection): void; - isUsed(): boolean; - reclustering(connection: BridgeClientConnection): void; - addMissedHeartbeat(): void; - removeMissedHeartbeat(): void; - resetMissedHeartbeats(): void; -} -type HeartbeatResponse = { - cpu: { - raw: { - user: number; - system: number; - }; - cpuPercent: string; - }; - memory: { - raw: { - rss: number; - heapTotal: number; - heapUsed: number; - external: number; - arrayBuffers: number; - }; - memoryPercent: string; - usage: number; - }; - ping: number; - shardPings: { - id: number; - ping: number; - status: number; - guilds: number; - members: number; - }[]; -}; - -declare class Bridge { - readonly port: number; - readonly server: Server; - readonly connectedClients: Map; - private readonly token; - private readonly intents; - private readonly shardsPerCluster; - private readonly clusterToStart; - private readonly reclusteringTimeoutInMs; - private readonly clusterCalculator; - private readonly eventMap; - constructor(port: number, token: string, intents: GatewayIntentsString[], shardsPerCluster: number, clusterToStart: number, reclusteringTimeoutInMs: number); - start(): void; - private interval; - private checkRecluster; - private heartbeat; - private checkCreate; - private createCluster; - startListening(): void; - sendMessageToClient(clientId: string, message: unknown): void; - private getTotalShards; - on(event: K, listener: BridgeEventListeners[K]): void; - getClusters(): BridgeClientCluster[]; - stopAllInstances(): Promise; - stopAllInstancesWithRestart(): Promise; - moveCluster(instance: BridgeClientConnection, cluster: BridgeClientCluster): Promise; - stopInstance(instance: BridgeClientConnection, recluster?: boolean): Promise; - sendRequestToGuild(cluster: BridgeClientCluster, guildID: Snowflake, data: unknown, timeout?: number): Promise; -} -type BridgeEventListeners = { - 'CLUSTER_READY': ((cluster: BridgeClientCluster, guilds: number, members: number) => void) | undefined; - 'CLUSTER_STOPPED': ((cluster: BridgeClientCluster) => void) | undefined; - 'CLUSTER_SPAWNED': ((cluster: BridgeClientCluster, connection: BridgeClientConnection) => void) | undefined; - 'CLUSTER_RECLUSTER': ((cluster: BridgeClientCluster, newConnection: BridgeClientConnection, oldConnection: BridgeClientConnection) => void) | undefined; - 'CLUSTER_HEARTBEAT_FAILED': ((cluster: BridgeClientCluster, error: unknown) => void) | undefined; - 'CLIENT_CONNECTED': ((client: BridgeClientConnection) => void) | undefined; - 'CLIENT_DISCONNECTED': ((client: BridgeClientConnection, reason: string) => void) | undefined; - 'ERROR': ((error: string) => void) | undefined; - 'CLIENT_STOP': ((instance: BridgeClientConnection) => void) | undefined; -}; - -/** - * Manages the calculation and distribution of clusters for a Discord bot sharding system. - * This class is responsible for creating clusters with their assigned shards, - * tracking which clusters are in use, and providing methods to retrieve available clusters. - */ -declare class ClusterCalculator { - /** The total number of clusters to initialize */ - private readonly clusterToStart; - /** The number of shards that each cluster will manage */ - private readonly shardsPerCluster; - /** List of all clusters managed by this calculator */ - readonly clusterList: BridgeClientCluster[]; - /** - * Creates a new ClusterCalculator and initializes the clusters. - * - * @param clusterToStart - The number of clusters to create - * @param shardsPerCluster - The number of shards each cluster will manage - */ - constructor(clusterToStart: number, shardsPerCluster: number); - /** - * Calculates and initializes all clusters with their assigned shards. - * Each cluster is assigned a sequential range of shard IDs based on its cluster index. - */ - private calculateClusters; - /** - * Retrieves the next available (unused) cluster and marks it as used. - * - * @returns The next available cluster, or undefined if all clusters are in use - */ - getNextCluster(): BridgeClientCluster | undefined; - /** - * Retrieves multiple available clusters up to the specified count. - * Each returned cluster is marked as used. - * - * @param count - The maximum number of clusters to retrieve - * @returns An array of available clusters (may be fewer than requested if not enough are available) - */ - getNextClusters(count: number): BridgeClientCluster[]; - /** - * Sets the used status of a specific cluster by its ID. - * - * @param clusterID - The ID of the cluster to update - * @param connection - The connection to associate with the cluster - */ - clearClusterConnection(clusterID: number): void; - getClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[]; - getOldClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[]; - checkAllClustersConnected(): boolean; - findMostAndLeastClustersForConnections(connectedClients: BridgeClientConnection[]): { - most: BridgeClientConnection | undefined; - least: BridgeClientConnection | undefined; - }; - getClusterWithLowestLoad(connectedClients: Map): BridgeClientConnection | undefined; - getClusterOfShard(shardID: number): BridgeClientCluster | undefined; -} - -declare class Cluster { - readonly instanceID: number; - readonly clusterID: number; - readonly shardList: number[]; - readonly totalShards: number; - readonly token: string; - readonly intents: GatewayIntentsString[]; - eventManager: EventManager; - client: T; - onSelfDestruct?: () => void; - private readonly eventMap; - constructor(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]); - static initial(): Cluster; - triggerReady(guilds: number, members: number): void; - triggerError(e: any): void; - private wait; - private _onMessage; - private _onRequest; - on(event: K, listener: ClusterEventListeners[K]): void; - sendMessage(data: unknown): void; - sendRequest(data: unknown, timeout?: number): Promise; - broadcastEval(fn: (cluster: T) => Result, timeout?: number): Promise; - sendMessageToClusterOfGuild(guildID: string, message: unknown): void; - sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout?: number): Promise; -} -type ClusterEventListeners = { - message: (message: unknown) => void; - request: (message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void; - CLUSTER_READY: () => void; -}; - -type ClusterProcessState = 'starting' | 'running' | 'stopped'; -declare class ClusterProcess { - readonly child: ChildProcess; - readonly eventManager: EventManager; - readonly id: number; - readonly shardList: number[]; - readonly totalShards: number; - status: ClusterProcessState; - readonly createdAt: number; - private _onMessage?; - private _onRequest?; - constructor(id: number, child: ChildProcess, shardList: number[], totalShards: number); - onMessage(callback: (message: unknown) => void): void; - onRequest(callback: (message: unknown) => unknown): void; - sendMessage(data: unknown): void; - sendRequest(data: unknown, timeout?: number): Promise; -} - -declare class ShardingUtil { - static getShardIDForGuild(guildID: string, totalShards: number): number; -} - -declare abstract class BotInstance { - private readonly entryPoint; - private readonly execArgv; - readonly clients: Map; - protected constructor(entryPoint: string, execArgv?: string[]); - protected readonly eventMap: BotInstanceEventListeners; - protected startProcess(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]): void; - protected killProcess(client: ClusterProcess, reason: string): void; - protected abstract setClusterStopped(client: ClusterProcess, reason: string): void; - protected abstract setClusterReady(client: ClusterProcess, guilds: number, members: number): void; - protected abstract setClusterSpawned(client: ClusterProcess): void; - abstract start(): void; - private onMessage; - protected abstract onRequest(client: ClusterProcess, message: any): Promise; - on(event: K, listener: BotInstanceEventListeners[K]): void; - sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout?: number): Promise; - sendRequestToCluster(cluster: ClusterProcess, message: unknown, timeout?: number): Promise; -} -type BotInstanceEventListeners = { - 'message': ((client: ClusterProcess, message: unknown) => void) | undefined; - 'request': ((client: ClusterProcess, message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined; - 'PROCESS_KILLED': ((client: ClusterProcess, reason: string, processKilled: boolean) => void) | undefined; - 'PROCESS_SELF_DESTRUCT_ERROR': ((client: ClusterProcess, reason: string, error: unknown) => void) | undefined; - 'PROCESS_SPAWNED': ((client: ClusterProcess) => void) | undefined; - 'PROCESS_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined; - 'CLUSTER_READY': ((client: ClusterProcess) => void) | undefined; - 'CLUSTER_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined; - 'CLUSTER_RECLUSTER': ((client: ClusterProcess) => void) | undefined; - 'ERROR': ((error: string) => void) | undefined; - 'BRIDGE_CONNECTION_ESTABLISHED': (() => void) | undefined; - 'BRIDGE_CONNECTION_CLOSED': ((reason: string) => void) | undefined; - 'BRIDGE_CONNECTION_STATUS_CHANGE': ((status: number) => void) | undefined; - 'INSTANCE_STOP': (() => void) | undefined; - 'INSTANCE_STOPPED': (() => void) | undefined; - 'SELF_CHECK_SUCCESS': (() => void) | undefined; - 'SELF_CHECK_ERROR': ((error: string) => void) | undefined; - 'SELF_CHECK_RECEIVED': ((data: { - clusterList: number[]; - }) => void) | undefined; -}; - -declare enum BridgeConnectionStatus { - CONNECTED = 0, - DISCONNECTED = 1 -} -declare class ManagedInstance extends BotInstance { - private readonly host; - private readonly port; - private readonly instanceID; - private eventManager; - private connectionStatus; - private data; - private dev; - constructor(entryPoint: string, host: string, port: number, instanceID: number, data: unknown, execArgv?: string[], dev?: boolean); - start(): void; - private selfCheck; - protected setClusterStopped(client: ClusterProcess, reason: string): void; - protected setClusterReady(client: ClusterProcess, guilds: number, members: number): void; - protected setClusterSpawned(client: ClusterProcess): void; - private onClusterCreate; - private onClusterStop; - private onClusterRecluster; - protected onRequest(client: ClusterProcess, message: any): Promise; - private onBridgeRequest; - stopInstance(): void; -} - -declare class StandaloneInstance extends BotInstance { - private readonly totalClusters; - private readonly shardsPerCluster; - readonly token: string; - readonly intents: GatewayIntentsString[]; - constructor(entryPoint: string, shardsPerCluster: number, totalClusters: number, token: string, intents: GatewayIntentsString[], execArgv?: string[]); - get totalShards(): number; - private calculateClusters; - start(): void; - protected setClusterStopped(client: ClusterProcess, reason: string): void; - protected setClusterReady(client: ClusterProcess): void; - protected setClusterSpawned(client: ClusterProcess): void; - private restartProcess; - protected onRequest(client: ClusterProcess, message: any): Promise; -} - -export { BotInstance, type BotInstanceEventListeners, Bridge, BridgeClientCluster, BridgeClientClusterConnectionStatus, BridgeClientConnection, BridgeClientConnectionStatus, BridgeConnectionStatus, type BridgeEventListeners, Cluster, ClusterCalculator, type ClusterEventListeners, ClusterProcess, type ClusterProcessState, EventManager, type EventPayload, type HeartbeatResponse, ManagedInstance, ShardingUtil, StandaloneInstance }; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 7617ac6..0000000 --- a/dist/index.js +++ /dev/null @@ -1,1576 +0,0 @@ -"use strict"; -var __create = Object.create; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __getProtoOf = Object.getPrototypeOf; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); -}; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( - // If the importer is in node compatibility mode or this is not an ESM - // file that has been converted to a CommonJS file using a Babel- - // compatible transform (i.e. "__esModule" has not been set), then set - // "default" to the CommonJS "module.exports" for node compatibility. - isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, - mod -)); -var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); - -// src/index.ts -var index_exports = {}; -__export(index_exports, { - BotInstance: () => BotInstance, - Bridge: () => Bridge, - BridgeClientCluster: () => BridgeClientCluster, - BridgeClientClusterConnectionStatus: () => BridgeClientClusterConnectionStatus, - BridgeClientConnection: () => BridgeClientConnection, - BridgeClientConnectionStatus: () => BridgeClientConnectionStatus, - BridgeConnectionStatus: () => BridgeConnectionStatus, - Cluster: () => Cluster, - ClusterCalculator: () => ClusterCalculator, - ClusterProcess: () => ClusterProcess, - EventManager: () => EventManager, - ManagedInstance: () => ManagedInstance, - ShardingUtil: () => ShardingUtil, - StandaloneInstance: () => StandaloneInstance -}); -module.exports = __toCommonJS(index_exports); - -// src/bridge/BridgeClientCluster.ts -var BridgeClientClusterConnectionStatus = /* @__PURE__ */ ((BridgeClientClusterConnectionStatus2) => { - BridgeClientClusterConnectionStatus2["REQUESTING"] = "requesting"; - BridgeClientClusterConnectionStatus2["STARTING"] = "starting"; - BridgeClientClusterConnectionStatus2["CONNECTED"] = "connected"; - BridgeClientClusterConnectionStatus2["RECLUSTERING"] = "reclustering"; - BridgeClientClusterConnectionStatus2["DISCONNECTED"] = "disconnected"; - return BridgeClientClusterConnectionStatus2; -})(BridgeClientClusterConnectionStatus || {}); -var BridgeClientCluster = class { - clusterID; - shardList; - connectionStatus = "disconnected" /* DISCONNECTED */; - connection; - oldConnection; - missedHeartbeats = 0; - heartbeatResponse; - heartbeatPending = false; - startedAt; - constructor(clusterID, shardList) { - this.clusterID = clusterID; - this.shardList = shardList; - } - setConnection(connection) { - if (connection == void 0) { - this.connectionStatus = "disconnected" /* DISCONNECTED */; - this.connection = void 0; - return; - } - if (this.connection) { - throw new Error(`Connection already set for cluster ${this.clusterID}`); - } - this.connectionStatus = "requesting" /* REQUESTING */; - this.connection = connection; - } - setOldConnection(connection) { - this.oldConnection = connection; - } - isUsed() { - return this.connection != void 0 && this.connectionStatus !== "disconnected" /* DISCONNECTED */; - } - reclustering(connection) { - this.connectionStatus = "reclustering" /* RECLUSTERING */; - this.oldConnection = this.connection; - this.connection = connection; - } - addMissedHeartbeat() { - this.missedHeartbeats++; - } - removeMissedHeartbeat() { - if (this.missedHeartbeats > 0) { - this.missedHeartbeats--; - } - } - resetMissedHeartbeats() { - this.missedHeartbeats = 0; - } -}; - -// src/general/EventManager.ts -var EventManager = class { - pendingPayloads = /* @__PURE__ */ new Map(); - // Track per-request timeout handles so we can clear them on resolve/reject - pendingTimeouts = /* @__PURE__ */ new Map(); - _send; - _on; - _request; - constructor(send, on, request) { - this._send = send; - this._on = on; - this._request = request; - } - async send(data) { - return this._send({ - id: crypto.randomUUID(), - type: "message", - data - }); - } - async request(payload, timeout) { - const id = crypto.randomUUID(); - return new Promise((resolve, reject) => { - this._send({ - id, - type: "request", - data: payload - }); - this.pendingPayloads.set(id, { - resolve, - reject - }); - const t = setTimeout(() => { - if (this.pendingPayloads.has(id)) { - this.pendingPayloads.delete(id); - this.pendingTimeouts.delete(id); - reject({ - error: `Request with id ${id} timed out` - }); - } - }, timeout); - this.pendingTimeouts.set(id, t); - }); - } - receive(possiblePayload) { - if (typeof possiblePayload !== "object" || possiblePayload === null) { - return; - } - const payload = possiblePayload; - if (!payload.id || !payload.type) { - return; - } - if (payload.type === "message") { - this._on(payload.data); - return; - } - if (payload.type === "response") { - const resolve = this.pendingPayloads.get(payload.id)?.resolve; - if (resolve) { - resolve(payload.data); - this.pendingPayloads.delete(payload.id); - const to = this.pendingTimeouts.get(payload.id); - if (to) clearTimeout(to); - this.pendingTimeouts.delete(payload.id); - } - return; - } - if (payload.type === "response_error") { - const reject = this.pendingPayloads.get(payload.id)?.reject; - if (reject) { - reject(payload.data); - this.pendingPayloads.delete(payload.id); - const to = this.pendingTimeouts.get(payload.id); - if (to) clearTimeout(to); - this.pendingTimeouts.delete(payload.id); - } - return; - } - if (payload.type === "request") { - const data = this._request(payload.data); - if (data instanceof Promise) { - data.then((result2) => { - this._send({ - id: payload.id, - type: "response", - data: result2 - }); - }).catch((error) => { - this._send({ - id: payload.id, - type: "response_error", - data: error - }); - }); - } else { - this._send({ - id: payload.id, - type: "response", - data - }); - } - return; - } - } - // Reject and clear all pending requests to avoid memory leaks when a connection/process closes - close(reason) { - if (this.pendingPayloads.size === 0 && this.pendingTimeouts.size === 0) return; - const err = { error: reason || "EventManager closed" }; - for (const [id, handlers] of this.pendingPayloads.entries()) { - try { - handlers.reject(err); - } catch (_) { - } - this.pendingPayloads.delete(id); - const to = this.pendingTimeouts.get(id); - if (to) clearTimeout(to); - this.pendingTimeouts.delete(id); - } - for (const to of this.pendingTimeouts.values()) { - clearTimeout(to); - } - this.pendingTimeouts.clear(); - } -}; - -// src/bridge/BridgeClientConnection.ts -var BridgeClientConnectionStatus = /* @__PURE__ */ ((BridgeClientConnectionStatus2) => { - BridgeClientConnectionStatus2["READY"] = "ready"; - BridgeClientConnectionStatus2["PENDING_STOP"] = "pending_stop"; - return BridgeClientConnectionStatus2; -})(BridgeClientConnectionStatus || {}); -var BridgeClientConnection = class { - instanceID; - eventManager; - connection; - data; - connectionStatus = "ready" /* READY */; - dev = false; - establishedAt = Date.now(); - _onMessage; - _onRequest; - constructor(instanceID, connection, data, dev) { - this.instanceID = instanceID; - this.connection = connection; - this.data = data; - this.dev = dev || false; - this.eventManager = new EventManager((message2) => { - if (!this.connection?.connection?.closed) { - return this.connection.send(message2); - } - return Promise.reject(new Error("Connection is closed, cannot send message")); - }, (message2) => { - if (this._onMessage) { - this._onMessage(message2); - } - }, (message2) => { - if (this._onRequest) { - return this._onRequest(message2); - } - return void 0; - }); - } - messageReceive(message2) { - this.eventManager.receive(message2); - } - onRequest(callback) { - this._onRequest = callback; - } - onMessage(callback) { - this._onMessage = callback; - } -}; - -// src/bridge/Bridge.ts -var import_net_ipc = require("net-ipc"); - -// src/bridge/ClusterCalculator.ts -var ClusterCalculator = class { - /** The total number of clusters to initialize */ - clusterToStart; - /** The number of shards that each cluster will manage */ - shardsPerCluster; - /** List of all clusters managed by this calculator */ - clusterList = []; - /** - * Creates a new ClusterCalculator and initializes the clusters. - * - * @param clusterToStart - The number of clusters to create - * @param shardsPerCluster - The number of shards each cluster will manage - */ - constructor(clusterToStart, shardsPerCluster) { - this.shardsPerCluster = shardsPerCluster; - this.clusterToStart = clusterToStart; - this.calculateClusters(); - } - /** - * Calculates and initializes all clusters with their assigned shards. - * Each cluster is assigned a sequential range of shard IDs based on its cluster index. - */ - calculateClusters() { - const clusters = /* @__PURE__ */ new Map(); - for (let i = 0; i < this.clusterToStart; i++) { - clusters.set(i, []); - for (let j = 0; j < this.shardsPerCluster; j++) { - clusters.get(i)?.push(i * this.shardsPerCluster + j); - } - } - for (let [clusterIndex, clusterShards] of clusters.entries()) { - this.clusterList.push(new BridgeClientCluster(clusterIndex, clusterShards)); - } - } - /** - * Retrieves the next available (unused) cluster and marks it as used. - * - * @returns The next available cluster, or undefined if all clusters are in use - */ - getNextCluster() { - for (const cluster of this.clusterList) { - if (!cluster.isUsed()) { - return cluster; - } - } - return void 0; - } - /** - * Retrieves multiple available clusters up to the specified count. - * Each returned cluster is marked as used. - * - * @param count - The maximum number of clusters to retrieve - * @returns An array of available clusters (may be fewer than requested if not enough are available) - */ - getNextClusters(count) { - const availableClusters = []; - for (const cluster of this.clusterList) { - if (!cluster.isUsed() && availableClusters.length < count) { - availableClusters.push(cluster); - } - } - return availableClusters; - } - /** - * Sets the used status of a specific cluster by its ID. - * - * @param clusterID - The ID of the cluster to update - * @param connection - The connection to associate with the cluster - */ - clearClusterConnection(clusterID) { - const cluster = this.clusterList.find((c) => c.clusterID === clusterID); - if (cluster) { - cluster.setConnection(void 0); - } - } - getClusterForConnection(connection) { - return this.clusterList.filter( - (cluster) => cluster.connection?.instanceID === connection.instanceID - ); - } - getOldClusterForConnection(connection) { - return this.clusterList.filter( - (cluster) => cluster.oldConnection?.instanceID === connection.instanceID - ); - } - checkAllClustersConnected() { - for (const cluster of this.clusterList) { - if (cluster.connectionStatus != "connected" /* CONNECTED */) { - return false; - } - } - return true; - } - findMostAndLeastClustersForConnections(connectedClients) { - const openClients = connectedClients.filter((x) => !x.dev); - const devClients = connectedClients.filter((x) => x.dev); - const summDevConnectedClusters = devClients.map((c) => this.getClusterForConnection(c).length).reduce((a, b) => a + b, 0); - let most; - let least; - let remainder = (this.clusterToStart - summDevConnectedClusters) % openClients.length || 0; - for (const client of openClients) { - const clusters = this.getClusterForConnection(client); - if (!most || clusters.length > this.getClusterForConnection(most).length) { - most = client; - } - if (!least || clusters.length < this.getClusterForConnection(least).length) { - least = client; - } - } - if (most && least) { - const mostCount = this.getClusterForConnection(most).length; - const leastCount = this.getClusterForConnection(least).length; - if (mostCount - leastCount <= remainder) { - return { most: void 0, least: void 0 }; - } - } - return { most, least }; - } - getClusterWithLowestLoad(connectedClients) { - let lowestLoadClient; - let lowestLoad = Infinity; - for (const client of connectedClients.values().filter((c) => c.connectionStatus === "ready" /* READY */ && !c.dev)) { - const clusters = this.getClusterForConnection(client); - const load = clusters.length; - if (load < lowestLoad) { - lowestLoad = load; - lowestLoadClient = client; - } - } - return lowestLoadClient; - } - getClusterOfShard(shardID) { - return this.clusterList.find((c) => c.shardList.includes(shardID)); - } -}; - -// src/general/ShardingUtil.ts -var ShardingUtil = class { - static getShardIDForGuild(guildID, totalShards) { - if (!guildID || totalShards <= 0) { - throw new Error("Invalid guild ID or total shards"); - } - return Number(BigInt(guildID) >> 22n) % totalShards; - } -}; - -// src/bridge/Bridge.ts -var Bridge = class { - port; - server; - connectedClients = /* @__PURE__ */ new Map(); - token; - intents; - shardsPerCluster = 1; - clusterToStart = 1; - reclusteringTimeoutInMs; - clusterCalculator; - eventMap = { - CLUSTER_READY: void 0, - CLUSTER_HEARTBEAT_FAILED: void 0, - CLUSTER_STOPPED: void 0, - CLIENT_CONNECTED: void 0, - CLIENT_DISCONNECTED: void 0, - CLUSTER_SPAWNED: void 0, - CLUSTER_RECLUSTER: void 0, - ERROR: void 0, - CLIENT_STOP: void 0 - }; - constructor(port, token, intents, shardsPerCluster, clusterToStart, reclusteringTimeoutInMs) { - this.port = port; - this.token = token; - this.intents = intents; - this.clusterToStart = clusterToStart; - this.shardsPerCluster = shardsPerCluster; - this.reclusteringTimeoutInMs = reclusteringTimeoutInMs; - this.clusterCalculator = new ClusterCalculator(this.clusterToStart, this.shardsPerCluster); - this.server = new import_net_ipc.Server({ - port: this.port - }); - } - start() { - this.server.start().then(() => { - this.startListening(); - }); - this.interval(); - } - interval() { - setInterval(() => { - this.checkCreate(); - this.checkRecluster(); - this.heartbeat(); - }, 5e3); - } - checkRecluster() { - const up = this.clusterCalculator.checkAllClustersConnected(); - if (!up) { - return; - } - const connectedClients = this.connectedClients.values().filter((c) => c.connectionStatus == "ready" /* READY */).filter((c) => !c.dev).filter((c) => c.establishedAt + this.reclusteringTimeoutInMs < Date.now()).toArray(); - const { most, least } = this.clusterCalculator.findMostAndLeastClustersForConnections(connectedClients); - if (most) { - const clusterToSteal = this.clusterCalculator.getClusterForConnection(most)[0] || void 0; - if (least && clusterToSteal) { - clusterToSteal.reclustering(least); - if (this.eventMap.CLUSTER_RECLUSTER) this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection); - this.createCluster(least, clusterToSteal, true); - return; - } - } - } - heartbeat() { - const clusters = this.clusterCalculator.clusterList; - clusters.forEach((cluster) => { - if (cluster.connection && cluster.connectionStatus == "connected" /* CONNECTED */ && !cluster.heartbeatPending) { - cluster.heartbeatPending = true; - cluster.connection.eventManager.request({ - type: "CLUSTER_HEARTBEAT", - data: { - clusterID: cluster.clusterID - } - }, 2e4).then((r) => { - cluster.removeMissedHeartbeat(); - cluster.heartbeatResponse = r; - }).catch((err) => { - if (this.eventMap.CLUSTER_HEARTBEAT_FAILED) this.eventMap.CLUSTER_HEARTBEAT_FAILED(cluster, err); - cluster.addMissedHeartbeat(); - if (cluster.missedHeartbeats > 7 && !cluster.connection?.dev) { - cluster.connection?.eventManager.send({ - type: "CLUSTER_STOP", - data: { - id: cluster.clusterID - } - }); - cluster.connectionStatus = "disconnected" /* DISCONNECTED */; - cluster.resetMissedHeartbeats(); - } - }).finally(() => { - cluster.heartbeatPending = false; - }); - } - }); - } - checkCreate() { - const optionalCluster = this.clusterCalculator.getNextCluster(); - if (!optionalCluster) { - return; - } - const lowestLoadClient = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients); - if (!lowestLoadClient) { - return; - } - this.createCluster(lowestLoadClient, optionalCluster); - } - createCluster(connection, cluster, recluster = false) { - cluster.resetMissedHeartbeats(); - cluster.heartbeatResponse = void 0; - if (!recluster) { - cluster.setConnection(connection); - } else { - cluster.oldConnection?.eventManager.send({ - type: "CLUSTER_RECLUSTER", - data: { - clusterID: cluster.clusterID - } - }); - } - if (this.eventMap.CLUSTER_SPAWNED) this.eventMap.CLUSTER_SPAWNED(cluster, connection); - connection.eventManager.send({ - type: "CLUSTER_CREATE", - data: { - clusterID: cluster.clusterID, - instanceID: connection.instanceID, - totalShards: this.getTotalShards(), - shardList: cluster.shardList, - token: this.token, - intents: this.intents - } - }); - } - startListening() { - this.server.on("connect", (connection, payload) => { - const id = payload?.id; - const data = payload.data; - const dev = payload?.dev || false; - if (!id) { - connection.close("Invalid payload", false); - return; - } - if (this.connectedClients.values().some((client) => client.instanceID === id)) { - connection.close("Already connected", false); - return; - } - const bridgeConnection = new BridgeClientConnection(payload.id, connection, data, dev); - if (this.eventMap.CLIENT_CONNECTED) this.eventMap.CLIENT_CONNECTED(bridgeConnection); - bridgeConnection.onMessage((m2) => { - if (m2.type == "CLUSTER_SPAWNED") { - const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); - if (cluster) { - cluster.connectionStatus = "starting" /* STARTING */; - } - return; - } - if (m2.type == "CLUSTER_READY") { - const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); - if (cluster) { - cluster.startedAt = Date.now(); - if (this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(cluster, m2.data.guilds || 0, m2.data.members || 0); - cluster.connectionStatus = "connected" /* CONNECTED */; - if (cluster.oldConnection) { - cluster.oldConnection.eventManager.send({ - type: "CLUSTER_STOP", - data: { - id: cluster.clusterID - } - }); - cluster.oldConnection = void 0; - } - } - return; - } - if (m2.type == "CLUSTER_STOPPED") { - const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); - if (cluster) { - cluster.startedAt = void 0; - if (this.eventMap.CLUSTER_STOPPED) this.eventMap.CLUSTER_STOPPED(cluster); - cluster.setConnection(void 0); - } - return; - } - if (m2.type == "INSTANCE_STOP") { - this.stopInstance(bridgeConnection); - } - return; - }); - bridgeConnection.onRequest((m2) => { - if (m2.type == "REDIRECT_REQUEST_TO_GUILD") { - const guildID = m2.guildID; - const shardID = ShardingUtil.getShardIDForGuild(guildID, this.getTotalShards()); - const cluster = this.clusterCalculator.getClusterOfShard(shardID); - if (!cluster) { - return Promise.reject(new Error("cluster not found")); - } - if (cluster.connectionStatus != "connected" /* CONNECTED */) { - return Promise.reject(new Error("cluster not connected.")); - } - if (!cluster.connection?.eventManager) { - return Promise.reject(new Error("no connection defined.")); - } - return cluster.connection.eventManager.request({ - type: "REDIRECT_REQUEST_TO_GUILD", - clusterID: cluster.clusterID, - guildID, - data: m2.data - }, 5e3); - } - if (m2.type == "BROADCAST_EVAL") { - const responses = Promise.all( - this.connectedClients.values().map((c) => { - return c.eventManager.request({ - type: "BROADCAST_EVAL", - data: m2.data - }, 5e3); - }) - ); - return new Promise((resolve, reject) => { - responses.then((r) => { - resolve(r.flatMap((f) => f)); - }).catch(reject); - }); - } - if (m2.type == "SELF_CHECK") { - return { - clusterList: [ - ...this.clusterCalculator.getClusterForConnection(bridgeConnection).map((c) => c.clusterID), - ...this.clusterCalculator.getOldClusterForConnection(bridgeConnection).map((c) => c.clusterID) - ] - }; - } - return Promise.reject(new Error("unknown type")); - }); - this.connectedClients.set(connection.id, bridgeConnection); - }); - this.server.on("disconnect", (connection, reason) => { - const closedConnection = this.connectedClients.get(connection.id); - if (!closedConnection) { - return; - } - const clusters = this.clusterCalculator.getClusterForConnection(closedConnection); - for (const cluster of clusters) { - this.clusterCalculator.clearClusterConnection(cluster.clusterID); - } - this.connectedClients.delete(connection.id); - if (this.eventMap.CLIENT_DISCONNECTED) this.eventMap.CLIENT_DISCONNECTED(closedConnection, reason); - }); - this.server.on("message", (message2, connection) => { - this.sendMessageToClient(connection.id, message2); - }); - } - sendMessageToClient(clientId, message2) { - if (!this.connectedClients.has(clientId)) { - return; - } - const client = this.connectedClients.get(clientId); - if (client) { - client.messageReceive(message2); - } - } - getTotalShards() { - return this.shardsPerCluster * this.clusterToStart; - } - on(event, listener) { - this.eventMap[event] = listener; - } - getClusters() { - return this.clusterCalculator.clusterList; - } - async stopAllInstances() { - const instances = Array.from(this.connectedClients.values()); - for (const instance of instances) { - instance.connectionStatus = "pending_stop" /* PENDING_STOP */; - } - for (const instance of instances) { - await this.stopInstance(instance, false); - } - } - async stopAllInstancesWithRestart() { - const instances = Array.from(this.connectedClients.values()); - for (const instance of instances) { - await this.stopInstance(instance); - await new Promise((resolve) => { - setTimeout(async () => { - resolve(); - }, 1e3 * 10); - }); - } - } - async moveCluster(instance, cluster) { - cluster.reclustering(instance); - this.createCluster(instance, cluster, true); - } - async stopInstance(instance, recluster = true) { - if (this.eventMap.CLIENT_STOP) this.eventMap.CLIENT_STOP(instance); - instance.connectionStatus = "pending_stop" /* PENDING_STOP */; - let clusterToSteal; - await instance.eventManager.send({ - type: "INSTANCE_STOP" - }); - if (recluster) { - while ((clusterToSteal = this.clusterCalculator.getClusterForConnection(instance).filter((c) => c.connectionStatus === "connected" /* CONNECTED */ || c.connectionStatus == "starting" /* STARTING */ || c.connectionStatus == "reclustering" /* RECLUSTERING */)[0]) !== void 0) { - if (clusterToSteal.connectionStatus != "connected" /* CONNECTED */) break; - const least = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients); - if (!least) { - if (this.eventMap.ERROR) { - this.eventMap.ERROR("Reclustering failed: No least cluster found."); - } - await instance.eventManager.send({ - type: "CLUSTER_STOP", - data: { - id: clusterToSteal.clusterID - } - }); - clusterToSteal.connection = void 0; - clusterToSteal.connectionStatus = "disconnected" /* DISCONNECTED */; - continue; - } - clusterToSteal.reclustering(least); - if (this.eventMap.CLUSTER_RECLUSTER) { - this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection); - } - this.createCluster(least, clusterToSteal, true); - } - return new Promise((resolve, reject) => { - const interval = setInterval(async () => { - const cluster = this.clusterCalculator.getOldClusterForConnection(instance)[0] || void 0; - if (!cluster) { - clearInterval(interval); - await instance.eventManager.send({ - type: "INSTANCE_STOPPED" - }); - await instance.connection.close("Instance stopped.", false); - resolve(); - return; - } - }, 1e3); - }); - } else { - for (const cluster of this.clusterCalculator.getClusterForConnection(instance)) { - await instance.eventManager.send({ - type: "CLUSTER_STOP", - data: { - id: cluster.clusterID - } - }); - } - await instance.eventManager.send({ - type: "INSTANCE_STOPPED" - }); - await instance.connection.close("Instance stopped.", false); - } - } - sendRequestToGuild(cluster, guildID, data, timeout = 5e3) { - if (!cluster.connection) { - return Promise.reject(new Error("No connection defined for cluster " + cluster.clusterID)); - } - return cluster.connection.eventManager.request({ - type: "REDIRECT_REQUEST_TO_GUILD", - clusterID: cluster.clusterID, - guildID, - data - }, timeout); - } -}; - -// src/cluster/Cluster.ts -var import_os = __toESM(require("os")); -var Cluster = class _Cluster { - instanceID; - clusterID; - shardList = []; - totalShards; - token; - intents; - eventManager; - client; - onSelfDestruct; - eventMap = { - message: void 0, - request: void 0, - CLUSTER_READY: void 0 - }; - constructor(instanceID, clusterID, shardList, totalShards, token, intents) { - this.instanceID = instanceID; - this.clusterID = clusterID; - this.shardList = shardList; - this.totalShards = totalShards; - this.token = token; - this.intents = intents; - this.eventManager = new EventManager((message2) => { - return new Promise((resolve, reject) => { - if (typeof process.send !== "function") { - reject(new Error("Process does not support sending messages")); - return; - } - process.send?.(message2, void 0, void 0, (error) => { - if (error) { - reject(error); - } else { - resolve(); - } - }); - }); - }, (message2) => { - this._onMessage(message2); - }, (message2) => { - return this._onRequest(message2); - }); - process.on("message", (message2) => { - this.eventManager.receive(message2); - }); - } - static initial() { - const args = process.env; - if (args.SHARD_LIST == void 0 || args.INSTANCE_ID == void 0 || args.TOTAL_SHARDS == void 0 || args.TOKEN == void 0 || args.INTENTS == void 0 || args.CLUSTER_ID == void 0) { - throw new Error("Missing required environment variables"); - } - const shardList = args.SHARD_LIST.split(",").map(Number); - const totalShards = Number(args.TOTAL_SHARDS); - const instanceID = Number(args.INSTANCE_ID); - const clusterID = Number(args.CLUSTER_ID); - const token = args.TOKEN; - const intents = args.INTENTS.split(",").map((i) => i.trim()); - return new _Cluster(instanceID, clusterID, shardList, totalShards, token, intents); - } - triggerReady(guilds, members) { - this.eventManager.send({ - type: "CLUSTER_READY", - id: this.clusterID, - guilds, - members - }); - if (this.eventMap?.CLUSTER_READY) { - this.eventMap?.CLUSTER_READY(); - } - } - triggerError(e) { - this.eventManager.send({ - type: "CLUSTER_ERROR", - id: this.clusterID - }); - } - async wait(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - _onMessage(message2) { - const m2 = message2; - if (m2.type == "CUSTOM" && this.eventMap.message) { - this.eventMap.message(m2.data); - } - } - _onRequest(message) { - const m = message; - if (m.type == "CUSTOM" && this.eventMap.request) { - return new Promise((resolve, reject) => { - this.eventMap.request(m.data, resolve, reject); - }); - } else if (m.type == "CLUSTER_HEARTBEAT") { - const startTime = process.hrtime.bigint(); - const startUsage = process.cpuUsage(); - (async () => { - await this.wait(500); - })(); - const endTime = process.hrtime.bigint(); - const usageDiff = process.cpuUsage(startUsage); - const elapsedTimeUs = Number((endTime - startTime) / 1000n); - const totalCPUTime = usageDiff.user + usageDiff.system; - const cpuCount = import_os.default.cpus().length; - const cpuPercent = totalCPUTime / (elapsedTimeUs * cpuCount) * 100; - let shardPings = []; - try { - const shards = this.client.ws.shards; - if (shards) { - shards.forEach((shard) => { - shardPings.push({ - id: shard.id, - ping: shard.ping, - status: shard.status, - guilds: this.client.guilds.cache.filter((g) => g.shardId === shard.id).size, - members: this.client.guilds.cache.filter((g) => g.shardId === shard.id).reduce((acc, g) => acc + g.memberCount, 0) - }); - this.client.shard?.fetchClientValues("uptime", shard.id).then((values) => { - shardPings[shard.id]["uptime"] = values; - console.log(values); - }).catch((e) => { - }); - }); - } - } catch (_) { - } - return { - cpu: { raw: process.cpuUsage(), cpuPercent: cpuPercent.toFixed(2) }, - memory: { - raw: process.memoryUsage(), - memoryPercent: (process.memoryUsage().heapUsed / process.memoryUsage().heapTotal * 100).toFixed(2) + "%", - usage: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + "MB" - }, - ping: this.client.ws.ping, - shardPings - }; - } else if (m.type == "BROADCAST_EVAL") { - const broadcast = message; - const fn = eval(`(${broadcast.data})`); - const result = fn(this.client); - if (result instanceof Promise) { - return new Promise((resolve, reject) => { - result.then((res) => { - resolve(res); - }).catch((err) => { - reject(err); - }); - }); - } else { - return result; - } - } else if (m.type == "SELF_DESTRUCT") { - if (this.onSelfDestruct) { - this.onSelfDestruct(); - } - } - return void 0; - } - on(event, listener) { - this.eventMap[event] = listener; - } - sendMessage(data) { - this.eventManager.send({ - type: "CUSTOM", - data - }); - } - sendRequest(data, timeout = 5e3) { - return this.eventManager.request({ - type: "CUSTOM", - data - }, timeout); - } - broadcastEval(fn2, timeout = 2e4) { - return this.eventManager.request({ - type: "BROADCAST_EVAL", - data: fn2.toString() - }, timeout); - } - sendMessageToClusterOfGuild(guildID, message2) { - if (this.eventManager) { - this.eventManager.send({ - type: "REDIRECT_MESSAGE_TO_GUILD", - guildID, - data: message2 - }); - } - } - sendRequestToClusterOfGuild(guildID, message2, timeout = 5e3) { - return new Promise((resolve, reject) => { - if (this.eventManager) { - this.eventManager.request({ - type: "REDIRECT_REQUEST_TO_GUILD", - guildID, - data: message2 - }, timeout).then((response) => { - resolve(response); - }).catch((error) => { - reject(error); - }); - } else { - reject(new Error("Event manager is not initialized")); - } - }); - } -}; - -// src/cluster/ClusterProcess.ts -var ClusterProcess = class { - child; - eventManager; - id; - shardList; - totalShards; - status; - createdAt = Date.now(); - _onMessage; - _onRequest; - constructor(id, child, shardList, totalShards) { - this.id = id; - this.child = child; - this.shardList = shardList; - this.totalShards = totalShards; - this.status = "starting"; - this.eventManager = new EventManager((message2) => { - return new Promise((resolve, reject) => { - this.child.send(message2, (error) => { - if (error) { - reject(error); - } else { - resolve(); - } - }); - }); - }, (message2) => { - if (this._onMessage) { - this._onMessage(message2); - } - }, (message2) => { - if (this._onRequest) { - return this._onRequest(message2); - } - return void 0; - }); - this.child.on("message", (message2) => { - this.eventManager.receive(message2); - }); - this.child.on("exit", () => { - this.eventManager.close("child process exited"); - }); - this.child.on("error", () => { - this.eventManager.close("child process error"); - }); - } - onMessage(callback) { - this._onMessage = callback; - } - onRequest(callback) { - this._onRequest = callback; - } - sendMessage(data) { - this.eventManager.send({ - type: "CUSTOM", - data - }); - } - sendRequest(data, timeout = 5e3) { - return this.eventManager.request({ - type: "CUSTOM", - data - }, timeout); - } -}; - -// src/instance/BotInstance.ts -var import_child_process = require("child_process"); -var BotInstance = class { - entryPoint; - execArgv; - clients = /* @__PURE__ */ new Map(); - constructor(entryPoint, execArgv) { - this.entryPoint = entryPoint; - this.execArgv = execArgv ?? []; - } - eventMap = { - "message": void 0, - "request": void 0, - "PROCESS_KILLED": void 0, - "PROCESS_SELF_DESTRUCT_ERROR": void 0, - "PROCESS_SPAWNED": void 0, - "ERROR": void 0, - "PROCESS_ERROR": void 0, - "CLUSTER_READY": void 0, - "CLUSTER_ERROR": void 0, - "CLUSTER_RECLUSTER": void 0, - "BRIDGE_CONNECTION_ESTABLISHED": void 0, - "BRIDGE_CONNECTION_CLOSED": void 0, - "BRIDGE_CONNECTION_STATUS_CHANGE": void 0, - "INSTANCE_STOP": void 0, - "INSTANCE_STOPPED": void 0, - "SELF_CHECK_SUCCESS": void 0, - "SELF_CHECK_ERROR": void 0, - "SELF_CHECK_RECEIVED": void 0 - }; - startProcess(instanceID, clusterID, shardList, totalShards, token, intents) { - try { - const child = (0, import_child_process.fork)(this.entryPoint, { - env: { - INSTANCE_ID: instanceID.toString(), - CLUSTER_ID: clusterID.toString(), - SHARD_LIST: shardList.join(","), - TOTAL_SHARDS: totalShards.toString(), - TOKEN: token, - INTENTS: intents.join(","), - FORCE_COLOR: "true" - }, - stdio: "inherit", - execArgv: this.execArgv, - silent: false, - detached: true - }); - const client = new ClusterProcess(clusterID, child, shardList, totalShards); - child.stdout?.on("data", (data) => { - process.stdout.write(data); - }); - child.stderr?.on("data", (data) => { - process.stderr.write(data); - }); - child.on("spawn", () => { - if (this.eventMap.PROCESS_SPAWNED) this.eventMap.PROCESS_SPAWNED(client); - this.setClusterSpawned(client); - this.clients.set(clusterID, client); - client.onMessage((message2) => { - this.onMessage(client, message2); - }); - client.onRequest((message2) => { - return this.onRequest(client, message2); - }); - }); - child.on("error", (err) => { - if (this.eventMap.PROCESS_ERROR) this.eventMap.PROCESS_ERROR(client, err); - }); - child.on("exit", (err) => { - if (client.status !== "stopped") { - client.status = "stopped"; - this.killProcess(client, `Process exited: ${err?.message}`); - } - }); - } catch (error) { - throw new Error(`Failed to start process for cluster ${clusterID}: ${error instanceof Error ? error.message : String(error)}`); - } - } - killProcess(client, reason) { - client.status = "stopped"; - client.eventManager.request({ - type: "SELF_DESTRUCT", - reason - }, 5e3).catch(() => { - if (this.eventMap.PROCESS_SELF_DESTRUCT_ERROR) this.eventMap.PROCESS_SELF_DESTRUCT_ERROR(client, reason, "Cluster didnt respond to shot-call."); - }).finally(() => { - if (client.child && client.child.pid) { - if (client.child.kill("SIGKILL")) { - if (this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, true); - } else { - if (this.eventMap.ERROR) this.eventMap.ERROR(`Failed to kill process for cluster ${client.id}`); - client.child.kill("SIGKILL"); - } - try { - process.kill(-client.child.pid); - } catch { - } - } else { - if (this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, false); - } - this.clients.delete(client.id); - this.setClusterStopped(client, reason); - }); - } - onMessage(client, message2) { - if (message2.type === "CLUSTER_READY") { - client.status = "running"; - if (this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(client); - this.setClusterReady(client, message2.guilds || 0, message2.members || 0); - } - if (message2.type === "CLUSTER_ERROR") { - client.status = "stopped"; - if (this.eventMap.CLUSTER_ERROR) this.eventMap.CLUSTER_ERROR(client, message2.error); - this.killProcess(client, "Cluster error: " + message2.error); - } - if (message2.type == "CUSTOM" && this.eventMap.message) { - this.eventMap.message(client, message2.data); - } - } - on(event, listener) { - this.eventMap[event] = listener; - } - sendRequestToClusterOfGuild(guildID, message2, timeout = 5e3) { - return new Promise((resolve, reject) => { - for (const client of this.clients.values()) { - const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); - if (client.shardList.includes(shardID)) { - client.eventManager.request({ - type: "CUSTOM", - data: message2 - }, timeout).then(resolve).catch(reject); - return; - } - } - reject(new Error(`No cluster found for guild ${guildID}`)); - }); - } - sendRequestToCluster(cluster, message2, timeout = 5e3) { - return new Promise((resolve, reject) => { - cluster.eventManager.request({ - type: "CUSTOM", - data: message2 - }, timeout).then(resolve).catch(reject); - return; - }); - } -}; - -// src/instance/ManagedInstance.ts -var import_net_ipc2 = require("net-ipc"); -var BridgeConnectionStatus = /* @__PURE__ */ ((BridgeConnectionStatus2) => { - BridgeConnectionStatus2[BridgeConnectionStatus2["CONNECTED"] = 0] = "CONNECTED"; - BridgeConnectionStatus2[BridgeConnectionStatus2["DISCONNECTED"] = 1] = "DISCONNECTED"; - return BridgeConnectionStatus2; -})(BridgeConnectionStatus || {}); -var ManagedInstance = class extends BotInstance { - host; - port; - instanceID; - eventManager; - connectionStatus = 1 /* DISCONNECTED */; - data; - dev = false; - constructor(entryPoint, host, port, instanceID, data, execArgv, dev) { - super(entryPoint, execArgv); - this.host = host; - this.port = port; - this.instanceID = instanceID; - this.data = data; - this.dev = dev || false; - } - start() { - const client = new import_net_ipc2.Client({ - host: this.host, - port: this.port, - reconnect: true, - retries: 100 - }); - this.eventManager = new EventManager((message2) => { - if (client.status == 3) { - return client.send(message2); - } - return Promise.reject(new Error("Client is not ready to send messages")); - }, (message2) => { - const m2 = message2; - if (m2.type == "CLUSTER_CREATE") { - this.onClusterCreate(m2.data); - } else if (m2.type == "CLUSTER_STOP") { - this.onClusterStop(m2.data); - } else if (m2.type == "CLUSTER_RECLUSTER") { - this.onClusterRecluster(m2.data); - } else if (m2.type == "INSTANCE_STOP") { - if (this.eventMap.INSTANCE_STOP) { - this.eventMap.INSTANCE_STOP(); - } - } else if (m2.type == "INSTANCE_STOPPED") { - if (this.eventMap.INSTANCE_STOPPED) { - this.eventMap.INSTANCE_STOPPED(); - } - } - }, (message2) => { - return this.onBridgeRequest(message2); - }); - setInterval(() => { - if (this.connectionStatus == 0 /* CONNECTED */) { - this.selfCheck(); - } - }, 2500); - client.connect({ - id: this.instanceID, - dev: this.dev, - data: this.data - }).then((_) => { - if (this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED(); - this.connectionStatus = 0 /* CONNECTED */; - client.on("message", (message2) => { - this.eventManager?.receive(message2); - }); - client.on("close", (reason) => { - if (this.eventMap.BRIDGE_CONNECTION_CLOSED) this.eventMap.BRIDGE_CONNECTION_CLOSED(reason); - if (this.connectionStatus == 0 /* CONNECTED */) { - this.clients.forEach((client2) => { - this.killProcess(client2, "Bridge connection closed"); - }); - } - this.connectionStatus = 1 /* DISCONNECTED */; - }); - client.on("status", (status) => { - if (this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE) this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE(status); - if (status == 4) { - if (this.connectionStatus == 0 /* CONNECTED */) { - this.clients.forEach((client2) => { - this.killProcess(client2, "Bridge connection closed"); - }); - } - this.connectionStatus = 1 /* DISCONNECTED */; - } else if (status == 3) { - this.connectionStatus = 0 /* CONNECTED */; - if (this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED(); - } - }); - }); - } - selfCheck() { - this.eventManager.request({ - type: "SELF_CHECK" - }, 1e3 * 60).then((r) => { - const response = r; - if (this.eventMap.SELF_CHECK_RECEIVED) { - this.eventMap.SELF_CHECK_RECEIVED(response); - } - const startingClusters = this.clients.values().filter((c) => c.status == "starting").toArray(); - startingClusters.forEach((c) => { - if (Date.now() - c.createdAt > 10 * 60 * 1e3) { - this.killProcess(c, "Cluster took too long to start"); - } - }); - const wrongClusters = this.clients.values().filter((c) => !response.clusterList.includes(c.id)).toArray(); - if (wrongClusters.length > 0) { - if (this.eventMap.SELF_CHECK_ERROR) { - this.eventMap.SELF_CHECK_ERROR(`Self check found wrong clusters: ${wrongClusters.map((c) => c.id).join(", ")}`); - } - wrongClusters.forEach((c) => { - this.killProcess(c, "Self check found wrong cluster"); - }); - } else { - if (this.eventMap.SELF_CHECK_SUCCESS) { - this.eventMap.SELF_CHECK_SUCCESS(); - } - } - }).catch((err) => { - if (this.eventMap.SELF_CHECK_ERROR) { - this.eventMap.SELF_CHECK_ERROR(`Self check failed: ${err}`); - } - }); - } - setClusterStopped(client, reason) { - this.eventManager?.send({ - type: "CLUSTER_STOPPED", - data: { - id: client.id, - reason - } - }).catch(() => { - return null; - }); - } - setClusterReady(client, guilds, members) { - this.eventManager?.send({ - type: "CLUSTER_READY", - data: { - id: client.id, - guilds, - members - } - }); - } - setClusterSpawned(client) { - this.eventManager?.send({ - type: "CLUSTER_SPAWNED", - data: { - id: client.id - } - }); - } - onClusterCreate(message2) { - const m2 = message2; - if (this.clients.has(m2.clusterID)) { - this.eventManager?.send({ - type: "CLUSTER_STOPPED", - data: { - id: m2.clusterID, - reason: "Cluster already exists" - } - }).catch(() => { - return null; - }); - return; - } - this.startProcess(this.instanceID, m2.clusterID, m2.shardList, m2.totalShards, m2.token, m2.intents); - } - onClusterStop(message2) { - const m2 = message2; - const cluster = this.clients.get(m2.id); - if (cluster) { - this.killProcess(cluster, `Request to stop cluster ${m2.id}`); - } - } - onClusterRecluster(message2) { - const m2 = message2; - const cluster = this.clients.get(m2.clusterID); - if (this.eventMap.CLUSTER_RECLUSTER && cluster) { - this.eventMap.CLUSTER_RECLUSTER(cluster); - } - } - onRequest(client, message2) { - if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { - const guildID = message2.guildID; - const data = message2.data; - const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); - if (client.shardList.includes(shardID)) { - return client.eventManager.request({ - type: "CUSTOM", - data - }, 5e3); - } else { - return this.eventManager.request({ - type: "REDIRECT_REQUEST_TO_GUILD", - guildID, - data - }, 5e3); - } - } - if (message2.type == "BROADCAST_EVAL") { - return this.eventManager.request({ - type: "BROADCAST_EVAL", - data: message2.data - }, 5e3); - } - if (message2.type == "CUSTOM" && this.eventMap.request) { - return new Promise((resolve, reject) => { - this.eventMap.request(client, message2.data, resolve, reject); - }); - } - return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); - } - onBridgeRequest(message2) { - if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { - const clusterID = message2.clusterID; - const data = message2.data; - const cluster = this.clients.get(clusterID); - if (cluster) { - return cluster.eventManager.request({ - type: "CUSTOM", - data - }, 5e3); - } else { - return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`)); - } - } else if (message2.type == "CLUSTER_HEARTBEAT") { - const clusterID = message2.data.clusterID; - const cluster = this.clients.get(clusterID); - if (cluster) { - return new Promise((resolve, reject) => { - cluster.eventManager.request({ - type: "CLUSTER_HEARTBEAT" - }, 15e3).then((r) => { - resolve(r); - }).catch((err) => { - reject(err); - }); - }); - } else { - return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`)); - } - } else if (message2.type == "BROADCAST_EVAL") { - return Promise.all(this.clients.values().filter((c) => c.status == "running").map((c) => { - return c.eventManager.request({ - type: "BROADCAST_EVAL", - data: message2.data - }, 5e3); - })); - } - return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); - } - stopInstance() { - this.eventManager?.send({ - type: "INSTANCE_STOP" - }); - } -}; - -// src/instance/StandaloneInstance.ts -var StandaloneInstance = class extends BotInstance { - totalClusters; - shardsPerCluster; - token; - intents; - constructor(entryPoint, shardsPerCluster, totalClusters, token, intents, execArgv) { - super(entryPoint, execArgv); - this.shardsPerCluster = shardsPerCluster; - this.totalClusters = totalClusters; - this.token = token; - this.intents = intents; - } - get totalShards() { - return this.shardsPerCluster * this.totalClusters; - } - calculateClusters() { - const clusters = {}; - for (let i = 0; i < this.totalClusters; i++) { - clusters[i] = []; - for (let j = 0; j < this.shardsPerCluster; j++) { - clusters[i].push(i * this.shardsPerCluster + j); - } - } - return clusters; - } - start() { - const clusters = this.calculateClusters(); - for (const [id, shardList] of Object.entries(clusters)) { - this.startProcess(1, Number(id), shardList, this.totalShards, this.token, this.intents); - } - } - setClusterStopped(client, reason) { - this.clients.delete(client.id); - this.restartProcess(client); - } - setClusterReady(client) { - } - setClusterSpawned(client) { - } - restartProcess(client) { - this.startProcess(1, client.id, client.shardList, this.totalShards, this.token, this.intents); - } - onRequest(client, message2) { - if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { - const guildID = message2.guildID; - const data = message2.data; - const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); - if (client.shardList.includes(shardID)) { - return client.eventManager.request({ - type: "CUSTOM", - data - }, 5e3); - } else { - return Promise.reject(new Error(`Shard ID ${shardID} not found in cluster ${client.id} for guild ${guildID}`)); - } - } - if (message2.type == "BROADCAST_EVAL") { - return Promise.all( - this.clients.values().map((c) => { - return c.eventManager.request({ - type: "BROADCAST_EVAL", - data: message2.data - }, 5e3); - }) - ); - } - if (message2.type == "CUSTOM" && this.eventMap.request) { - return new Promise((resolve, reject) => { - this.eventMap.request(client, message2.data, resolve, reject); - }); - } - return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); - } -}; -// Annotate the CommonJS export names for ESM import in node: -0 && (module.exports = { - BotInstance, - Bridge, - BridgeClientCluster, - BridgeClientClusterConnectionStatus, - BridgeClientConnection, - BridgeClientConnectionStatus, - BridgeConnectionStatus, - Cluster, - ClusterCalculator, - ClusterProcess, - EventManager, - ManagedInstance, - ShardingUtil, - StandaloneInstance -}); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map deleted file mode 100644 index cbf0ec8..0000000 --- a/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/index.ts","../src/bridge/BridgeClientCluster.ts","../src/general/EventManager.ts","../src/bridge/BridgeClientConnection.ts","../src/bridge/Bridge.ts","../src/bridge/ClusterCalculator.ts","../src/general/ShardingUtil.ts","../src/cluster/Cluster.ts","../src/cluster/ClusterProcess.ts","../src/instance/BotInstance.ts","../src/instance/ManagedInstance.ts","../src/instance/StandaloneInstance.ts"],"sourcesContent":["export * from './bridge/BridgeClientCluster';\nexport * from './bridge/BridgeClientConnection';\nexport * from './bridge/Bridge';\nexport * from './bridge/ClusterCalculator';\n\nexport * from './cluster/Cluster';\nexport * from './cluster/ClusterProcess';\n\nexport * from './general/EventManager';\nexport * from './general/ShardingUtil';\nexport * from './general/EventPayload';\n\nexport * from './instance/BotInstance';\nexport * from './instance/ManagedInstance';\nexport * from './instance/StandaloneInstance';","import {BridgeClientConnection} from \"./BridgeClientConnection\";\n\nexport enum BridgeClientClusterConnectionStatus {\n REQUESTING = 'requesting',\n STARTING = 'starting',\n CONNECTED = 'connected',\n RECLUSTERING = 'reclustering',\n DISCONNECTED = 'disconnected',\n}\n\nexport class BridgeClientCluster {\n public readonly clusterID: number;\n public readonly shardList: number[];\n public connectionStatus: BridgeClientClusterConnectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n\n public connection?: BridgeClientConnection;\n\n public oldConnection?: BridgeClientConnection;\n\n public missedHeartbeats: number = 0;\n\n public heartbeatResponse?: HeartbeatResponse;\n\n public heartbeatPending = false;\n\n public startedAt?: number;\n\n constructor(clusterID: number, shardList: number[]) {\n this.clusterID = clusterID;\n this.shardList = shardList;\n }\n\n setConnection(connection?: BridgeClientConnection): void {\n if(connection == undefined){\n this.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n this.connection = undefined;\n return;\n }\n\n if (this.connection) {\n throw new Error(`Connection already set for cluster ${this.clusterID}`);\n }\n\n this.connectionStatus = BridgeClientClusterConnectionStatus.REQUESTING;\n this.connection = connection;\n }\n\n setOldConnection(connection?: BridgeClientConnection): void {\n this.oldConnection = connection;\n }\n\n isUsed(): boolean {\n return this.connection != undefined && this.connectionStatus !== BridgeClientClusterConnectionStatus.DISCONNECTED;\n }\n\n reclustering(connection: BridgeClientConnection): void {\n this.connectionStatus = BridgeClientClusterConnectionStatus.RECLUSTERING;\n this.oldConnection = this.connection;\n this.connection = connection;\n }\n\n addMissedHeartbeat(): void {\n this.missedHeartbeats++;\n }\n\n removeMissedHeartbeat(): void {\n if (this.missedHeartbeats > 0) {\n this.missedHeartbeats--;\n }\n }\n\n resetMissedHeartbeats(): void {\n this.missedHeartbeats = 0;\n }\n}\n\nexport type HeartbeatResponse = {\n cpu: {\n raw: {\n user: number,\n system: number,\n }\n cpuPercent: string\n },\n memory: {\n raw: {\n rss: number,\n heapTotal: number,\n heapUsed: number,\n external: number,\n arrayBuffers: number,\n },\n memoryPercent: string\n usage: number\n },\n ping: number,\n shardPings: {\n id: number,\n ping: number,\n status: number,\n guilds: number,\n members: number\n }[]\n}","import {EventPayload} from \"./EventPayload\";\n\nexport class EventManager {\n\n private pendingPayloads = new Map void;\n reject: (error: unknown) => void;\n }>();\n\n // Track per-request timeout handles so we can clear them on resolve/reject\n private pendingTimeouts = new Map>();\n\n private readonly _send: (payload: EventPayload) => Promise;\n\n private readonly _on: (payload: unknown) => void;\n\n private readonly _request: (payload: unknown) => unknown;\n\n constructor(send: (payload: EventPayload) => Promise, on: (message: unknown) => void, request: (message: unknown) => unknown) {\n this._send = send;\n this._on = on;\n this._request = request\n }\n\n async send(data: unknown) {\n return this._send({\n id: crypto.randomUUID(),\n type: 'message',\n data: data\n });\n }\n\n async request(payload: unknown, timeout: number): Promise {\n const id = crypto.randomUUID();\n\n return new Promise((resolve, reject) => {\n this._send({\n id: id,\n type: 'request',\n data: payload\n });\n\n this.pendingPayloads.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject\n });\n\n const t = setTimeout(() => {\n if (this.pendingPayloads.has(id)) {\n this.pendingPayloads.delete(id);\n this.pendingTimeouts.delete(id);\n reject({\n error: `Request with id ${id} timed out`,\n });\n }\n }, timeout);\n this.pendingTimeouts.set(id, t);\n })\n }\n\n receive(possiblePayload: unknown) {\n if (typeof possiblePayload !== 'object' || possiblePayload === null) {\n return;\n }\n\n const payload = possiblePayload as EventPayload;\n\n if (!payload.id || !payload.type) {\n return;\n }\n\n if (payload.type === 'message') {\n this._on(payload.data);\n return;\n }\n\n if (payload.type === 'response') {\n // Handle requests\n const resolve = this.pendingPayloads.get(payload.id)?.resolve;\n if (resolve) {\n resolve(payload.data);\n this.pendingPayloads.delete(payload.id);\n const to = this.pendingTimeouts.get(payload.id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(payload.id);\n }\n return;\n }\n\n if (payload.type === 'response_error') {\n // Handle requests\n const reject = this.pendingPayloads.get(payload.id)?.reject;\n if (reject) {\n reject(payload.data);\n this.pendingPayloads.delete(payload.id);\n const to = this.pendingTimeouts.get(payload.id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(payload.id);\n }\n return;\n }\n\n if (payload.type === 'request') {\n // Handle requests\n const data = this._request(payload.data);\n if(data instanceof Promise) {\n data.then((result) => {\n this._send({\n id: payload.id,\n type: 'response',\n data: result\n });\n }).catch((error) => {\n this._send({\n id: payload.id,\n type: 'response_error',\n data: error\n });\n });\n } else {\n this._send({\n id: payload.id,\n type: 'response',\n data: data\n });\n }\n return;\n }\n }\n\n // Reject and clear all pending requests to avoid memory leaks when a connection/process closes\n close(reason?: string) {\n if (this.pendingPayloads.size === 0 && this.pendingTimeouts.size === 0) return;\n const err = { error: reason || 'EventManager closed' };\n for (const [id, handlers] of this.pendingPayloads.entries()) {\n try { handlers.reject(err); } catch (_) { /* ignore */ }\n this.pendingPayloads.delete(id);\n const to = this.pendingTimeouts.get(id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(id);\n }\n // In case there are any stray timeouts with no pending payload\n for (const to of this.pendingTimeouts.values()) {\n clearTimeout(to);\n }\n this.pendingTimeouts.clear();\n }\n}\n\n","import {EventManager} from \"../general/EventManager\";\nimport {Connection} from \"net-ipc\";\n\nexport enum BridgeClientConnectionStatus {\n READY = 'ready',\n PENDING_STOP = 'pending_stop',\n}\nexport class BridgeClientConnection {\n public readonly instanceID: number;\n public readonly eventManager: EventManager;\n public readonly connection: Connection;\n public readonly data: unknown;\n public connectionStatus: BridgeClientConnectionStatus = BridgeClientConnectionStatus.READY;\n public readonly dev: boolean = false;\n public readonly establishedAt: number = Date.now();\n\n private _onMessage?: (message: unknown) => void;\n private _onRequest?: (message: unknown) => unknown;\n\n constructor(instanceID: number, connection: Connection, data: unknown, dev: boolean) {\n this.instanceID = instanceID;\n this.connection = connection;\n this.data = data;\n this.dev = dev || false;\n this.eventManager = new EventManager((message) => {\n if(!this.connection?.connection?.closed){\n return this.connection.send(message);\n }\n return Promise.reject(new Error('Connection is closed, cannot send message'));\n }, (message) => {\n if (this._onMessage) {\n this._onMessage(message);\n }\n }, (message) => {\n if (this._onRequest) {\n return this._onRequest(message);\n }\n return undefined;\n })\n }\n\n messageReceive(message: any) {\n this.eventManager.receive(message);\n }\n\n onRequest(callback: (message: unknown) => unknown) {\n this._onRequest = callback;\n }\n\n onMessage(callback: (message: unknown) => void) {\n this._onMessage = callback;\n }\n}","import {Server} from 'net-ipc';\nimport {BridgeClientConnection, BridgeClientConnectionStatus} from \"./BridgeClientConnection\";\nimport {GatewayIntentsString, Snowflake} from \"discord.js\";\nimport {ClusterCalculator} from \"./ClusterCalculator\";\nimport {BridgeClientCluster, BridgeClientClusterConnectionStatus, HeartbeatResponse} from \"./BridgeClientCluster\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport class Bridge {\n public readonly port: number;\n public readonly server: Server;\n public readonly connectedClients: Map = new Map();\n private readonly token: string;\n private readonly intents: GatewayIntentsString[];\n private readonly shardsPerCluster: number = 1;\n private readonly clusterToStart: number = 1\n private readonly reclusteringTimeoutInMs: number;\n\n private readonly clusterCalculator: ClusterCalculator;\n\n private readonly eventMap: BridgeEventListeners = {\n CLUSTER_READY: undefined, CLUSTER_HEARTBEAT_FAILED: undefined,\n CLUSTER_STOPPED: undefined, CLIENT_CONNECTED: undefined, CLIENT_DISCONNECTED: undefined,\n CLUSTER_SPAWNED: undefined, CLUSTER_RECLUSTER: undefined, ERROR: undefined,\n CLIENT_STOP: undefined\n }\n\n constructor(port: number, token: string, intents: GatewayIntentsString[], shardsPerCluster: number, clusterToStart: number, reclusteringTimeoutInMs: number) {\n this.port = port;\n this.token = token;\n this.intents = intents;\n this.clusterToStart = clusterToStart;\n this.shardsPerCluster = shardsPerCluster;\n this.reclusteringTimeoutInMs = reclusteringTimeoutInMs;\n\n this.clusterCalculator = new ClusterCalculator(this.clusterToStart, this.shardsPerCluster);\n\n this.server = new Server({\n port: this.port,\n })\n }\n\n public start(): void {\n this.server.start().then(() => {\n this.startListening();\n })\n\n this.interval();\n }\n\n private interval(): void {\n setInterval(() => {\n this.checkCreate();\n this.checkRecluster();\n this.heartbeat();\n }, 5000)\n }\n\n private checkRecluster(): void {\n // check if all clusters are used\n const up = this.clusterCalculator.checkAllClustersConnected()\n if (!up) {\n return;\n }\n\n const connectedClients: BridgeClientConnection[] = this.connectedClients.values()\n .filter(c => c.connectionStatus == BridgeClientConnectionStatus.READY)\n .filter(c => !c.dev)\n .filter(c => c.establishedAt + this.reclusteringTimeoutInMs < Date.now())\n .toArray();\n\n const {most, least} = this.clusterCalculator.findMostAndLeastClustersForConnections(connectedClients);\n if (most) {\n const clusterToSteal = this.clusterCalculator.getClusterForConnection(most)[0] || undefined;\n if (least && clusterToSteal) {\n clusterToSteal.reclustering(least);\n\n if(this.eventMap.CLUSTER_RECLUSTER) this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection!);\n this.createCluster(least, clusterToSteal, true);\n\n return;\n }\n }\n }\n\n private heartbeat(): void {\n const clusters = this.clusterCalculator.clusterList;\n\n clusters.forEach((cluster) => {\n if(cluster.connection && cluster.connectionStatus == BridgeClientClusterConnectionStatus.CONNECTED && !cluster.heartbeatPending) {\n cluster.heartbeatPending = true;\n cluster.connection.eventManager.request({\n type: 'CLUSTER_HEARTBEAT',\n data: {\n clusterID: cluster.clusterID\n }\n }, 20000).then((r) => {\n cluster.removeMissedHeartbeat();\n cluster.heartbeatResponse = r;\n }).catch((err) => {\n if(this.eventMap.CLUSTER_HEARTBEAT_FAILED) this.eventMap.CLUSTER_HEARTBEAT_FAILED(cluster, err)\n cluster.addMissedHeartbeat()\n\n if(cluster.missedHeartbeats > 7 && !cluster.connection?.dev){\n cluster.connection?.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n cluster.resetMissedHeartbeats()\n }\n }).finally(() => {\n cluster.heartbeatPending = false;\n })\n }\n });\n }\n\n private checkCreate(): void {\n const optionalCluster = this.clusterCalculator.getNextCluster();\n\n if (!optionalCluster) {\n return;\n }\n\n const lowestLoadClient = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients);\n if (!lowestLoadClient) {\n return;\n }\n\n this.createCluster(lowestLoadClient, optionalCluster)\n }\n\n private createCluster(connection: BridgeClientConnection, cluster: BridgeClientCluster, recluster = false) {\n cluster.resetMissedHeartbeats()\n cluster.heartbeatResponse = undefined;\n if (!recluster) {\n cluster.setConnection(connection)\n } else {\n cluster.oldConnection?.eventManager.send({\n type: 'CLUSTER_RECLUSTER',\n data: {\n clusterID: cluster.clusterID\n }\n })\n }\n if(this.eventMap.CLUSTER_SPAWNED) this.eventMap.CLUSTER_SPAWNED(cluster, connection)\n connection.eventManager.send({\n type: 'CLUSTER_CREATE',\n data: {\n clusterID: cluster.clusterID,\n instanceID: connection.instanceID,\n totalShards: this.getTotalShards(),\n shardList: cluster.shardList,\n token: this.token,\n intents: this.intents\n }\n });\n }\n\n public startListening(): void {\n this.server.on('connect', (connection, payload) => {\n const id = payload?.id;\n const data = payload.data as unknown;\n const dev = payload?.dev || false;\n if (!id) {\n connection.close('Invalid payload', false);\n return;\n }\n\n if (this.connectedClients.values().some(client => client.instanceID === id)) {\n connection.close('Already connected', false);\n return;\n }\n\n const bridgeConnection = new BridgeClientConnection(payload.id, connection, data, dev);\n if(this.eventMap.CLIENT_CONNECTED) this.eventMap.CLIENT_CONNECTED(bridgeConnection);\n\n bridgeConnection.onMessage((m: any) => {\n if (m.type == 'CLUSTER_SPAWNED') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.STARTING;\n }\n return;\n }\n\n if (m.type == 'CLUSTER_READY') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.startedAt = Date.now();\n if(this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(cluster, m.data.guilds || 0, m.data.members || 0);\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.CONNECTED;\n if (cluster.oldConnection) {\n cluster.oldConnection.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n cluster.oldConnection = undefined;\n }\n }\n return;\n }\n\n if (m.type == 'CLUSTER_STOPPED') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.startedAt = undefined;\n if(this.eventMap.CLUSTER_STOPPED) this.eventMap.CLUSTER_STOPPED(cluster);\n cluster.setConnection(undefined);\n }\n return;\n }\n\n if(m.type == \"INSTANCE_STOP\") {\n this.stopInstance(bridgeConnection);\n }\n\n return;\n })\n\n bridgeConnection.onRequest((m: any) => {\n if(m.type == 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = m.guildID;\n const shardID = ShardingUtil.getShardIDForGuild(guildID, this.getTotalShards());\n const cluster = this.clusterCalculator.getClusterOfShard(shardID);\n if(!cluster){\n return Promise.reject(new Error(\"cluster not found\"))\n }\n if(cluster.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED){\n return Promise.reject(new Error(\"cluster not connected.\"))\n }\n\n if(!cluster.connection?.eventManager){\n return Promise.reject(new Error(\"no connection defined.\"))\n }\n\n return cluster.connection.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n clusterID: cluster.clusterID,\n guildID: guildID,\n data: m.data\n }, 5000)\n }\n\n if(m.type == 'BROADCAST_EVAL') {\n const responses = Promise.all(\n this.connectedClients.values().map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: m.data,\n }, 5000);\n })\n )\n return new Promise((resolve, reject) => {\n responses.then((r) => {\n resolve(r.flatMap(f => f))\n }).catch(reject);\n })\n }\n\n if(m.type == 'SELF_CHECK') {\n return {\n clusterList: [\n ...this.clusterCalculator.getClusterForConnection(bridgeConnection).map(c => c.clusterID),\n ...this.clusterCalculator.getOldClusterForConnection(bridgeConnection).map(c => c.clusterID)\n ]\n }\n }\n\n return Promise.reject(new Error(\"unknown type\"))\n })\n\n this.connectedClients.set(connection.id, bridgeConnection)\n });\n\n this.server.on('disconnect', (connection, reason) => {\n const closedConnection = this.connectedClients.get(connection.id);\n if (!closedConnection) {\n return;\n }\n\n const clusters = this.clusterCalculator.getClusterForConnection(closedConnection);\n for (const cluster of clusters) {\n this.clusterCalculator.clearClusterConnection(cluster.clusterID);\n }\n\n this.connectedClients.delete(connection.id);\n if(this.eventMap.CLIENT_DISCONNECTED) this.eventMap.CLIENT_DISCONNECTED(closedConnection, reason);\n });\n\n this.server.on(\"message\", (message, connection) => {\n this.sendMessageToClient(connection.id, message);\n })\n }\n\n sendMessageToClient(clientId: string, message: unknown): void {\n if (!this.connectedClients.has(clientId)) {\n return;\n }\n\n const client = this.connectedClients.get(clientId);\n if (client) {\n client.messageReceive(message);\n }\n }\n\n private getTotalShards() {\n return this.shardsPerCluster * this.clusterToStart;\n }\n\n\n public on(event: K, listener: BridgeEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public getClusters() {\n return this.clusterCalculator.clusterList;\n }\n\n async stopAllInstances() {\n const instances = Array.from(this.connectedClients.values());\n for (const instance of instances) {\n instance.connectionStatus = BridgeClientConnectionStatus.PENDING_STOP;\n }\n\n for (const instance of instances) {\n await this.stopInstance(instance, false);\n }\n }\n\n async stopAllInstancesWithRestart() {\n const instances = Array.from(this.connectedClients.values());\n\n for (const instance of instances) {\n await this.stopInstance(instance);\n await new Promise((resolve) => {\n setTimeout(async () => {\n resolve();\n }, 1000 * 10);\n })\n }\n }\n\n async moveCluster(instance: BridgeClientConnection, cluster: BridgeClientCluster) {\n cluster.reclustering(instance);\n\n this.createCluster(instance, cluster, true);\n }\n\n async stopInstance(instance: BridgeClientConnection, recluster = true) {\n if(this.eventMap.CLIENT_STOP) this.eventMap.CLIENT_STOP(instance);\n instance.connectionStatus = BridgeClientConnectionStatus.PENDING_STOP;\n\n let clusterToSteal: BridgeClientCluster | undefined;\n\n await instance.eventManager.send({\n type: 'INSTANCE_STOP'\n });\n\n if(recluster) {\n while ((clusterToSteal = this.clusterCalculator.getClusterForConnection(instance).filter(c =>\n c.connectionStatus === BridgeClientClusterConnectionStatus.CONNECTED ||\n c.connectionStatus == BridgeClientClusterConnectionStatus.STARTING ||\n c.connectionStatus == BridgeClientClusterConnectionStatus.RECLUSTERING)[0]) !== undefined) {\n // skip if the cluster is not connected\n if(clusterToSteal.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED) break;\n\n const least = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients);\n if (!least) {\n if (this.eventMap.ERROR) {\n this.eventMap.ERROR(\"Reclustering failed: No least cluster found.\");\n }\n await instance.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: clusterToSteal.clusterID\n }\n });\n clusterToSteal.connection = undefined;\n clusterToSteal.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n continue;\n }\n\n clusterToSteal.reclustering(least);\n\n if (this.eventMap.CLUSTER_RECLUSTER) {\n this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection!);\n }\n\n this.createCluster(least, clusterToSteal, true);\n }\n\n return new Promise((resolve, reject) => {\n const interval = setInterval(async () => {\n const cluster = this.clusterCalculator.getOldClusterForConnection(instance)[0] || undefined;\n if (!cluster) {\n clearInterval(interval);\n await instance.eventManager.send({\n type: 'INSTANCE_STOPPED'\n })\n await instance.connection.close(\"Instance stopped.\", false);\n resolve();\n return;\n }\n }, 1000);\n })\n } else {\n for(const cluster of this.clusterCalculator.getClusterForConnection(instance)) {\n await instance.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n }\n\n\n await instance.eventManager.send({\n type: 'INSTANCE_STOPPED'\n })\n await instance.connection.close(\"Instance stopped.\", false);\n }\n }\n\n sendRequestToGuild(cluster: BridgeClientCluster, guildID: Snowflake, data: unknown, timeout = 5000): Promise {\n if(!cluster.connection){\n return Promise.reject(new Error(\"No connection defined for cluster \" + cluster.clusterID));\n }\n\n return cluster.connection.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n clusterID: cluster.clusterID,\n guildID: guildID,\n data: data\n }, timeout);\n }\n}\n\n\n\nexport type BridgeEventListeners = {\n 'CLUSTER_READY': ((cluster: BridgeClientCluster, guilds: number, members: number) => void) | undefined,\n 'CLUSTER_STOPPED': ((cluster: BridgeClientCluster) => void) | undefined,\n 'CLUSTER_SPAWNED': ((cluster: BridgeClientCluster, connection: BridgeClientConnection) => void) | undefined,\n 'CLUSTER_RECLUSTER': ((cluster: BridgeClientCluster, newConnection: BridgeClientConnection, oldConnection: BridgeClientConnection) => void) | undefined,\n 'CLUSTER_HEARTBEAT_FAILED': ((cluster: BridgeClientCluster, error: unknown) => void) | undefined,\n 'CLIENT_CONNECTED': ((client: BridgeClientConnection) => void) | undefined,\n 'CLIENT_DISCONNECTED': ((client: BridgeClientConnection, reason: string) => void) | undefined,\n 'ERROR': ((error: string) => void) | undefined,\n 'CLIENT_STOP': ((instance: BridgeClientConnection) => void) | undefined\n};","import {BridgeClientCluster, BridgeClientClusterConnectionStatus} from \"./BridgeClientCluster\";\nimport {BridgeClientConnection, BridgeClientConnectionStatus} from \"./BridgeClientConnection\";\n\n/**\n * Manages the calculation and distribution of clusters for a Discord bot sharding system.\n * This class is responsible for creating clusters with their assigned shards,\n * tracking which clusters are in use, and providing methods to retrieve available clusters.\n */\nexport class ClusterCalculator {\n /** The total number of clusters to initialize */\n private readonly clusterToStart: number;\n\n /** The number of shards that each cluster will manage */\n private readonly shardsPerCluster: number;\n\n /** List of all clusters managed by this calculator */\n public readonly clusterList: BridgeClientCluster[]= [];\n\n /**\n * Creates a new ClusterCalculator and initializes the clusters.\n * \n * @param clusterToStart - The number of clusters to create\n * @param shardsPerCluster - The number of shards each cluster will manage\n */\n constructor(clusterToStart: number, shardsPerCluster: number) {\n this.shardsPerCluster = shardsPerCluster;\n this.clusterToStart = clusterToStart;\n\n this.calculateClusters();\n }\n\n /**\n * Calculates and initializes all clusters with their assigned shards.\n * Each cluster is assigned a sequential range of shard IDs based on its cluster index.\n */\n private calculateClusters(): void {\n const clusters: Map = new Map();\n for (let i = 0; i < this.clusterToStart; i++) {\n clusters.set(i, []);\n for (let j = 0; j < this.shardsPerCluster; j++) {\n clusters.get(i)?.push(i * this.shardsPerCluster + j);\n }\n }\n\n for (let [clusterIndex, clusterShards] of clusters.entries()) {\n this.clusterList.push(new BridgeClientCluster(clusterIndex, clusterShards));\n }\n }\n\n /**\n * Retrieves the next available (unused) cluster and marks it as used.\n * \n * @returns The next available cluster, or undefined if all clusters are in use\n */\n public getNextCluster(): BridgeClientCluster | undefined {\n for (const cluster of this.clusterList) {\n if (!cluster.isUsed()) {\n return cluster;\n }\n }\n return undefined; // No available clusters\n }\n\n /**\n * Retrieves multiple available clusters up to the specified count.\n * Each returned cluster is marked as used.\n * \n * @param count - The maximum number of clusters to retrieve\n * @returns An array of available clusters (may be fewer than requested if not enough are available)\n */\n public getNextClusters(count: number): BridgeClientCluster[] {\n const availableClusters: BridgeClientCluster[] = [];\n for (const cluster of this.clusterList) {\n if (!cluster.isUsed() && availableClusters.length < count) {\n availableClusters.push(cluster);\n }\n }\n return availableClusters; // Returns the clusters that were found\n }\n\n /**\n * Sets the used status of a specific cluster by its ID.\n *\n * @param clusterID - The ID of the cluster to update\n * @param connection - The connection to associate with the cluster\n */\n public clearClusterConnection(clusterID: number): void {\n const cluster = this.clusterList.find(c => c.clusterID === clusterID);\n if (cluster) {\n cluster.setConnection(undefined);\n }\n }\n\n public getClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[] {\n return this.clusterList.filter(cluster =>\n cluster.connection?.instanceID === connection.instanceID\n );\n }\n\n public getOldClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[] {\n return this.clusterList.filter(cluster =>\n cluster.oldConnection?.instanceID === connection.instanceID\n );\n }\n\n public checkAllClustersConnected(): boolean {\n for (const cluster of this.clusterList) {\n if (cluster.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED){\n return false; // At least one cluster is not in use\n }\n }\n return true; // All clusters are in use\n }\n\n\n findMostAndLeastClustersForConnections(\n connectedClients: BridgeClientConnection[]\n ): {\n most: BridgeClientConnection | undefined,\n least: BridgeClientConnection | undefined\n } {\n\n const openClients = connectedClients.filter(x => !x.dev)\n\n const devClients = connectedClients.filter(x => x.dev)\n const summDevConnectedClusters = devClients.map(c => this.getClusterForConnection(c).length).reduce((a, b) => a + b, 0);\n\n let most: BridgeClientConnection | undefined;\n let least: BridgeClientConnection | undefined;\n let remainder = ((this.clusterToStart - summDevConnectedClusters) % openClients.length || 0);\n\n for (const client of openClients) {\n const clusters = this.getClusterForConnection(client);\n\n if (!most || clusters.length > this.getClusterForConnection(most).length) {\n most = client;\n }\n\n if (!least || clusters.length < this.getClusterForConnection(least).length) {\n least = client;\n }\n }\n\n if (most && least) {\n const mostCount = this.getClusterForConnection(most).length;\n const leastCount = this.getClusterForConnection(least).length;\n\n // Only recluster if the difference is greater than remainder\n if (mostCount - leastCount <= remainder) {\n return {most: undefined, least: undefined};\n }\n }\n\n return {most, least};\n }\n\n getClusterWithLowestLoad(connectedClients: Map): BridgeClientConnection | undefined {\n let lowestLoadClient: BridgeClientConnection | undefined;\n let lowestLoad = Infinity;\n\n for (const client of connectedClients.values().filter(c =>\n c.connectionStatus === BridgeClientConnectionStatus.READY && !c.dev)) {\n const clusters = this.getClusterForConnection(client);\n\n const load = clusters.length; // Assuming load is determined by the number of clusters assigned\n if (load < lowestLoad) {\n lowestLoad = load;\n lowestLoadClient = client;\n }\n }\n\n return lowestLoadClient; // Returns the client with the lowest load, or undefined if no clients are connected\n }\n\n getClusterOfShard(shardID: number) {\n return this.clusterList.find(c => c.shardList.includes(shardID));\n }\n}\n","export class ShardingUtil {\n public static getShardIDForGuild(guildID: string, totalShards: number): number {\n if (!guildID || totalShards <= 0) {\n throw new Error(\"Invalid guild ID or total shards\");\n }\n\n return Number(BigInt(guildID) >> 22n) % totalShards;\n }\n}","import {Client, GatewayIntentsString, Status} from \"discord.js\";\nimport {EventManager} from \"../general/EventManager\";\nimport os from \"os\";\nexport class Cluster {\n\n public readonly instanceID: number;\n\n public readonly clusterID: number;\n\n public readonly shardList: number[] = [];\n\n public readonly totalShards: number;\n\n public readonly token: string;\n\n public readonly intents: GatewayIntentsString[];\n\n public eventManager: EventManager;\n\n public client!: T;\n\n public onSelfDestruct?: () => void;\n\n private readonly eventMap: {\n 'message': ((message: unknown) => void) | undefined,\n 'request': ((message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined,\n 'CLUSTER_READY': (() => void) | undefined,\n } = {\n message: undefined, request: undefined, CLUSTER_READY: undefined,\n }\n\n constructor(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]) {\n this.instanceID = instanceID;\n this.clusterID = clusterID;\n this.shardList = shardList;\n this.totalShards = totalShards;\n this.token = token;\n this.intents = intents;\n this.eventManager = new EventManager((message: unknown) => {\n return new Promise((resolve, reject) => {\n if (typeof process.send !== 'function') {\n reject(new Error(\"Process does not support sending messages\"));\n return;\n }\n\n process.send?.(message, undefined, undefined, (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }, (message: unknown) => {\n this._onMessage(message);\n }, (message: unknown) => {\n return this._onRequest(message);\n });\n process.on(\"message\", (message) => {\n this.eventManager.receive(message);\n })\n }\n\n static initial(): Cluster {\n const args = process.env;\n\n if (args.SHARD_LIST == undefined || args.INSTANCE_ID == undefined || args.TOTAL_SHARDS == undefined || args.TOKEN == undefined || args.INTENTS == undefined || args.CLUSTER_ID == undefined) {\n throw new Error(\"Missing required environment variables\");\n }\n\n const shardList = args.SHARD_LIST.split(',').map(Number);\n\n const totalShards = Number(args.TOTAL_SHARDS);\n\n const instanceID = Number(args.INSTANCE_ID);\n const clusterID = Number(args.CLUSTER_ID);\n\n const token = args.TOKEN;\n\n const intents = args.INTENTS.split(',').map(i => i.trim()) as GatewayIntentsString[];\n\n return new Cluster(instanceID, clusterID, shardList, totalShards, token, intents);\n }\n\n triggerReady(guilds: number, members: number) {\n this.eventManager.send({\n type: 'CLUSTER_READY',\n id: this.clusterID,\n guilds: guilds,\n members: members,\n });\n\n if(this.eventMap?.CLUSTER_READY) {\n this.eventMap?.CLUSTER_READY();\n }\n }\n\n triggerError(e: any) {\n this.eventManager.send({\n type: 'CLUSTER_ERROR',\n id: this.clusterID,\n });\n }\n\n private async wait(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n private _onMessage(message: unknown): void {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CUSTOM' && this.eventMap.message) {\n this.eventMap.message!(m.data);\n }\n }\n\n private _onRequest(message: unknown): unknown {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(m.data, resolve, reject);\n });\n } else if(m.type == 'CLUSTER_HEARTBEAT'){\n const startTime = process.hrtime.bigint();\n const startUsage = process.cpuUsage();\n\n (async () => {\n await this.wait(500);\n })();\n\n const endTime = process.hrtime.bigint();\n const usageDiff = process.cpuUsage(startUsage);\n\n const elapsedTimeUs = Number((endTime - startTime) / 1000n);\n const totalCPUTime = usageDiff.user + usageDiff.system;\n\n const cpuCount = os.cpus().length;\n const cpuPercent = (totalCPUTime / (elapsedTimeUs * cpuCount)) * 100;\n\n // Collect per-shard ping information in addition to the overall ws ping\n let shardPings: { id: number, ping: number, status: Status, uptime?: unknown, guilds: number, members: number }[] = [];\n try {\n const shards = this.client.ws.shards;\n\n if(shards) {\n shards.forEach((shard) => {\n shardPings.push({ id: shard.id, ping: shard.ping, status: shard.status,\n guilds: this.client.guilds.cache.filter(g => g.shardId === shard.id).size,\n members: this.client.guilds.cache.filter(g => g.shardId === shard.id).reduce((acc, g) => acc + g.memberCount, 0)\n });\n\n this.client.shard?.fetchClientValues('uptime', shard.id).then(values => {\n shardPings[shard.id][\"uptime\"] = values\n console.log(values)\n }).catch(e => {\n\n })\n })\n }\n } catch (_) {\n // ignore and keep empty shardPings on failure\n }\n\n return {\n cpu: { raw: process.cpuUsage(), cpuPercent: cpuPercent.toFixed(2) },\n memory: { raw: process.memoryUsage(),\n memoryPercent: ((process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100).toFixed(2) + '%',\n usage: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + 'MB'\n },\n ping: this.client.ws.ping,\n shardPings: shardPings,\n }\n } else if(m.type == 'BROADCAST_EVAL'){\n const broadcast = message as { type: 'BROADCAST_EVAL', data: string }\n\n const fn = eval(`(${broadcast.data})`);\n\n const result = fn(this.client);\n if(result instanceof Promise){\n return new Promise((resolve, reject) => {\n result.then(res => {\n resolve(res);\n }).catch(err => {\n reject(err);\n });\n });\n } else {\n return result;\n }\n } else if(m.type == 'SELF_DESTRUCT') {\n if(this.onSelfDestruct) {\n this.onSelfDestruct();\n }\n }\n return undefined;\n }\n\n public on(event: K, listener: ClusterEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public sendMessage(data: unknown) {\n this.eventManager.send({\n type: 'CUSTOM',\n data: data,\n });\n }\n\n public sendRequest(data: unknown, timeout = 5000): Promise {\n return this.eventManager.request({\n type: 'CUSTOM',\n data: data,\n }, timeout);\n }\n\n public broadcastEval(fn: (cluster: T) => Result, timeout = 20000): Promise {\n return this.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: fn.toString(),\n }, timeout);\n }\n\n\n public sendMessageToClusterOfGuild(guildID: string, message: unknown): void {\n if (this.eventManager) {\n this.eventManager.send({\n type: 'REDIRECT_MESSAGE_TO_GUILD',\n guildID: guildID,\n data: message\n });\n }\n }\n\n public sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n if (this.eventManager) {\n this.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n guildID: guildID,\n data: message\n }, timeout).then((response) => {\n resolve(response);\n }).catch((error) => {\n reject(error);\n });\n } else {\n reject(new Error(\"Event manager is not initialized\"));\n }\n });\n }\n}\n\nexport type ClusterEventListeners = {\n message: (message: unknown) => void;\n request: (message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void;\n\n CLUSTER_READY: () => void;\n};","import {ChildProcess} from \"child_process\";\nimport {EventManager} from \"../general/EventManager\";\n\nexport type ClusterProcessState = 'starting' | 'running' | 'stopped';\n\nexport class ClusterProcess {\n public readonly child: ChildProcess;\n public readonly eventManager: EventManager;\n public readonly id: number;\n public readonly shardList: number[];\n public readonly totalShards: number;\n public status: ClusterProcessState;\n public readonly createdAt: number = Date.now();\n\n private _onMessage?: (message: unknown) => void;\n private _onRequest?: (message: unknown) => unknown;\n\n constructor(id: number, child: ChildProcess, shardList: number[], totalShards: number) {\n this.id = id;\n this.child = child;\n this.shardList = shardList;\n this.totalShards = totalShards;\n this.status = 'starting';\n this.eventManager = new EventManager((message) => {\n return new Promise((resolve, reject) => {\n this.child.send(message, (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n })\n }, (message) => {\n if (this._onMessage) {\n this._onMessage(message);\n }\n }, (message) => {\n if (this._onRequest) {\n return this._onRequest(message);\n }\n return undefined;\n })\n\n this.child.on('message', (message) => {\n this.eventManager.receive(message);\n });\n\n // Ensure we do not retain pending requests if the child dies or errors\n this.child.on('exit', () => {\n this.eventManager.close('child process exited');\n });\n this.child.on('error', () => {\n this.eventManager.close('child process error');\n });\n }\n\n onMessage(callback: (message: unknown) => void) {\n this._onMessage = callback;\n }\n\n onRequest(callback: (message: unknown) => unknown) {\n this._onRequest = callback;\n }\n\n public sendMessage(data: unknown) {\n this.eventManager.send({\n type: 'CUSTOM',\n data: data,\n });\n }\n\n public sendRequest(data: unknown, timeout = 5000): Promise {\n return this.eventManager.request({\n type: 'CUSTOM',\n data: data,\n }, timeout);\n }\n}","import {fork} from 'child_process';\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport abstract class BotInstance {\n\n private readonly entryPoint: string;\n\n private readonly execArgv: string[];\n\n public readonly clients: Map = new Map();\n\n protected constructor(entryPoint: string, execArgv?: string[]) {\n this.entryPoint = entryPoint;\n this.execArgv = execArgv ?? [];\n }\n\n protected readonly eventMap: BotInstanceEventListeners = {\n 'message': undefined,\n 'request': undefined,\n\n 'PROCESS_KILLED': undefined,\n 'PROCESS_SELF_DESTRUCT_ERROR': undefined,\n 'PROCESS_SPAWNED': undefined,\n 'ERROR': undefined,\n 'PROCESS_ERROR': undefined,\n 'CLUSTER_READY': undefined,\n 'CLUSTER_ERROR': undefined,\n 'CLUSTER_RECLUSTER': undefined,\n 'BRIDGE_CONNECTION_ESTABLISHED': undefined,\n 'BRIDGE_CONNECTION_CLOSED': undefined,\n 'BRIDGE_CONNECTION_STATUS_CHANGE': undefined,\n 'INSTANCE_STOP': undefined,\n 'INSTANCE_STOPPED': undefined,\n 'SELF_CHECK_SUCCESS': undefined,\n 'SELF_CHECK_ERROR': undefined,\n 'SELF_CHECK_RECEIVED': undefined,\n }\n\n protected startProcess(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]): void {\n try {\n const child = fork(this.entryPoint, {\n env: {\n INSTANCE_ID: instanceID.toString(),\n CLUSTER_ID: clusterID.toString(),\n SHARD_LIST: shardList.join(','),\n TOTAL_SHARDS: totalShards.toString(),\n TOKEN: token,\n INTENTS: intents.join(','),\n FORCE_COLOR: 'true'\n },\n stdio: 'inherit',\n execArgv: this.execArgv,\n silent: false,\n detached: true,\n })\n\n const client = new ClusterProcess(clusterID, child, shardList, totalShards);\n\n child.stdout?.on('data', (data) => {\n process.stdout.write(data);\n });\n\n child.stderr?.on('data', (data) => {\n process.stderr.write(data);\n });\n\n child.on(\"spawn\", () => {\n if(this.eventMap.PROCESS_SPAWNED) this.eventMap.PROCESS_SPAWNED(client);\n\n this.setClusterSpawned(client);\n\n this.clients.set(clusterID, client);\n\n client.onMessage((message) => {\n this.onMessage(client, message);\n })\n\n client.onRequest((message) => {\n return this.onRequest(client, message);\n });\n });\n\n child.on(\"error\", (err) => {\n if(this.eventMap.PROCESS_ERROR) this.eventMap.PROCESS_ERROR(client, err);\n })\n\n child.on(\"exit\", (err: Error) => {\n if(client.status !== 'stopped') {\n client.status = 'stopped';\n this.killProcess(client, `Process exited: ${err?.message}`);\n }\n })\n } catch (error) {\n throw new Error(`Failed to start process for cluster ${clusterID}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n protected killProcess(client: ClusterProcess, reason: string): void {\n client.status = 'stopped';\n\n client.eventManager.request({\n type: 'SELF_DESTRUCT',\n reason: reason\n }, 5000).catch(() => {\n if(this.eventMap.PROCESS_SELF_DESTRUCT_ERROR) this.eventMap.PROCESS_SELF_DESTRUCT_ERROR(client, reason, 'Cluster didnt respond to shot-call.');\n }).finally(() => {\n if (client.child && client.child.pid) {\n if(client.child.kill(\"SIGKILL\")) {\n if(this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, true);\n } else {\n if(this.eventMap.ERROR) this.eventMap.ERROR(`Failed to kill process for cluster ${client.id}`);\n client.child.kill(\"SIGKILL\");\n }\n try { process.kill(-client.child.pid) } catch {}\n } else {\n if(this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, false);\n }\n this.clients.delete(client.id);\n this.setClusterStopped(client, reason);\n })\n }\n\n protected abstract setClusterStopped(client: ClusterProcess, reason: string): void;\n\n protected abstract setClusterReady(client: ClusterProcess, guilds: number, members: number): void;\n\n protected abstract setClusterSpawned(client: ClusterProcess): void;\n\n public abstract start(): void;\n\n private onMessage(client: ClusterProcess, message: any): void {\n if(message.type === 'CLUSTER_READY') {\n client.status = 'running';\n if(this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(client);\n this.setClusterReady(client, message.guilds || 0, message.members || 0);\n }\n\n if (message.type === 'CLUSTER_ERROR') {\n client.status = 'stopped';\n if(this.eventMap.CLUSTER_ERROR) this.eventMap.CLUSTER_ERROR(client, message.error);\n this.killProcess(client, 'Cluster error: ' + message.error);\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.message) {\n this.eventMap.message!(client, message.data);\n }\n }\n\n protected abstract onRequest(client: ClusterProcess, message: any): Promise;\n\n public on(event: K, listener: BotInstanceEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n for (const client of this.clients.values()) {\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if (client.shardList.includes(shardID)) {\n client.eventManager.request({\n type: 'CUSTOM',\n data: message\n }, timeout).then(resolve).catch(reject);\n return;\n }\n }\n reject(new Error(`No cluster found for guild ${guildID}`));\n });\n }\n\n public sendRequestToCluster(cluster: ClusterProcess, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n cluster.eventManager.request({\n type: 'CUSTOM',\n data: message\n }, timeout).then(resolve).catch(reject);\n return;\n });\n }\n}\n\nexport type BotInstanceEventListeners = {\n 'message': ((client: ClusterProcess,message: unknown) => void) | undefined,\n 'request': ((client: ClusterProcess, message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined,\n\n 'PROCESS_KILLED': ((client: ClusterProcess, reason: string, processKilled: boolean) => void) | undefined,\n 'PROCESS_SELF_DESTRUCT_ERROR': ((client: ClusterProcess, reason: string, error: unknown) => void) | undefined,\n 'PROCESS_SPAWNED': ((client: ClusterProcess) => void) | undefined,\n 'PROCESS_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined,\n 'CLUSTER_READY': ((client: ClusterProcess) => void) | undefined,\n 'CLUSTER_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined,\n 'CLUSTER_RECLUSTER': ((client: ClusterProcess) => void) | undefined,\n 'ERROR': ((error: string) => void) | undefined,\n\n 'BRIDGE_CONNECTION_ESTABLISHED': (() => void) | undefined,\n 'BRIDGE_CONNECTION_CLOSED': ((reason: string) => void) | undefined,\n 'BRIDGE_CONNECTION_STATUS_CHANGE': ((status: number) => void) | undefined,\n 'INSTANCE_STOP': (() => void) | undefined,\n 'INSTANCE_STOPPED': (() => void) | undefined,\n\n 'SELF_CHECK_SUCCESS': (() => void) | undefined,\n 'SELF_CHECK_ERROR': ((error: string) => void) | undefined,\n 'SELF_CHECK_RECEIVED': ((data: { clusterList: number[] }) => void) | undefined,\n};","import {BotInstance} from \"./BotInstance\";\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {Client} from \"net-ipc\";\nimport {EventManager} from \"../general/EventManager\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport enum BridgeConnectionStatus {\n CONNECTED,\n DISCONNECTED,\n}\n\nexport class ManagedInstance extends BotInstance {\n\n private readonly host: string;\n\n private readonly port: number;\n\n private readonly instanceID: number;\n\n private eventManager!: EventManager;\n\n private connectionStatus: BridgeConnectionStatus = BridgeConnectionStatus.DISCONNECTED;\n\n private data: unknown;\n\n private dev: boolean = false;\n\n constructor(entryPoint: string, host: string, port: number, instanceID: number, data: unknown, execArgv?: string[], dev?: boolean) {\n super(entryPoint, execArgv);\n\n this.host = host;\n this.port = port;\n this.instanceID = instanceID;\n this.data = data;\n this.dev = dev || false;\n }\n\n public start() {\n const client = new Client({\n host: this.host,\n port: this.port,\n reconnect: true,\n retries: 100\n });\n\n this.eventManager = new EventManager((message) => {\n if(client.status == 3) {\n return client.send(message);\n }\n return Promise.reject(new Error('Client is not ready to send messages'));\n }, (message) => {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CLUSTER_CREATE') {\n this.onClusterCreate(m.data)\n } else if (m.type == 'CLUSTER_STOP') {\n this.onClusterStop(m.data);\n } else if(m.type == 'CLUSTER_RECLUSTER') {\n this.onClusterRecluster(m.data);\n } else if(m.type == 'INSTANCE_STOP') {\n if(this.eventMap.INSTANCE_STOP) {\n this.eventMap.INSTANCE_STOP();\n }\n } else if(m.type == 'INSTANCE_STOPPED') {\n if(this.eventMap.INSTANCE_STOPPED) {\n this.eventMap.INSTANCE_STOPPED();\n }\n }\n }, (message) => {\n return this.onBridgeRequest(message);\n });\n\n setInterval(() => {\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.selfCheck();\n }\n }, 2500); // 5 minutes\n\n client.connect({\n id: this.instanceID,\n dev: this.dev,\n data: this.data,\n }).then(_ => {\n if(this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED();\n this.connectionStatus = BridgeConnectionStatus.CONNECTED;\n\n client.on(\"message\", (message) => {\n this.eventManager?.receive(message);\n })\n client.on(\"close\", (reason) => {\n if(this.eventMap.BRIDGE_CONNECTION_CLOSED) this.eventMap.BRIDGE_CONNECTION_CLOSED(reason);\n\n // kill all\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.clients.forEach((client) => {\n this.killProcess(client, 'Bridge connection closed');\n });\n }\n this.connectionStatus = BridgeConnectionStatus.DISCONNECTED;\n });\n\n client.on(\"status\", (status) => {\n if(this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE) this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE(status);\n\n if(status == 4){\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.clients.forEach((client) => {\n this.killProcess(client, 'Bridge connection closed');\n });\n }\n this.connectionStatus = BridgeConnectionStatus.DISCONNECTED;\n } else if(status == 3){\n this.connectionStatus = BridgeConnectionStatus.CONNECTED;\n if(this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED();\n }\n });\n })\n }\n\n private selfCheck() {\n this.eventManager.request({\n type: 'SELF_CHECK'\n }, 1000 * 60).then((r) => {\n const response = r as { clusterList: number[] };\n\n if(this.eventMap.SELF_CHECK_RECEIVED) {\n this.eventMap.SELF_CHECK_RECEIVED(response);\n }\n\n const startingClusters = this.clients.values().filter(c => c.status == 'starting').toArray();\n startingClusters.forEach((c: ClusterProcess) => {\n if (Date.now() - c.createdAt > 10 * 60 * 1000) {\n this.killProcess(c, 'Cluster took too long to start');\n }\n })\n\n // check if there is an wrong cluster on this host\n const wrongClusters = this.clients.values().filter(c => !response.clusterList.includes(c.id)).toArray();\n if(wrongClusters.length > 0) {\n if(this.eventMap.SELF_CHECK_ERROR) {\n this.eventMap.SELF_CHECK_ERROR(`Self check found wrong clusters: ${wrongClusters.map(c => c.id).join(', ')}`);\n }\n wrongClusters.forEach(c => {\n this.killProcess(c, 'Self check found wrong cluster');\n });\n } else {\n if(this.eventMap.SELF_CHECK_SUCCESS) {\n this.eventMap.SELF_CHECK_SUCCESS();\n }\n }\n }).catch((err) => {\n if(this.eventMap.SELF_CHECK_ERROR) {\n this.eventMap.SELF_CHECK_ERROR(`Self check failed: ${err}`);\n }\n });\n }\n\n protected setClusterStopped(client: ClusterProcess, reason: string): void {\n this.eventManager?.send({\n type: 'CLUSTER_STOPPED',\n data: {\n id: client.id,\n reason: reason\n }\n }).catch(() => {\n return null\n });\n }\n\n protected setClusterReady(client: ClusterProcess, guilds: number, members: number): void {\n this.eventManager?.send({\n type: 'CLUSTER_READY',\n data: {\n id: client.id,\n guilds: guilds,\n members: members\n }\n });\n }\n\n protected setClusterSpawned(client: ClusterProcess): void {\n this.eventManager?.send({\n type: 'CLUSTER_SPAWNED',\n data: {\n id: client.id\n }\n });\n }\n\n private onClusterCreate(message: unknown){\n const m = message as { clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[] }\n\n if(this.clients.has(m.clusterID)) {\n this.eventManager?.send({\n type: 'CLUSTER_STOPPED',\n data: {\n id: m.clusterID,\n reason: 'Cluster already exists'\n }\n }).catch(() => {\n return null\n });\n return;\n }\n\n this.startProcess(this.instanceID, m.clusterID, m.shardList, m.totalShards, m.token, m.intents);\n }\n\n private onClusterStop(message: unknown) {\n const m = message as { id: number };\n const cluster = this.clients.get(m.id)\n if (cluster) {\n this.killProcess(cluster, `Request to stop cluster ${m.id}`);\n }\n }\n\n private onClusterRecluster(message: unknown) {\n const m = message as { clusterID: number }\n const cluster = this.clients.get(m.clusterID);\n if (this.eventMap.CLUSTER_RECLUSTER && cluster) {\n this.eventMap.CLUSTER_RECLUSTER(cluster);\n }\n }\n\n protected onRequest(client: ClusterProcess, message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = message.guildID;\n const data = message.data;\n\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if(client.shardList.includes(shardID)) {\n return client.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return this.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n guildID: guildID,\n data: data\n }, 5000)\n }\n }\n\n if(message.type == 'BROADCAST_EVAL') {\n return this.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data\n }, 5000)\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(client, message.data, resolve, reject);\n });\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n private onBridgeRequest(message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const clusterID = message.clusterID;\n const data = message.data;\n\n const cluster = this.clients.get(clusterID);\n if(cluster){\n return cluster.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`));\n }\n } else if(message.type == 'CLUSTER_HEARTBEAT'){\n const clusterID = message.data.clusterID;\n const cluster = this.clients.get(clusterID);\n if (cluster) {\n return new Promise((resolve, reject) => {\n cluster.eventManager.request({\n type: 'CLUSTER_HEARTBEAT'\n }, 15000).then((r) => {\n resolve(r);\n }).catch((err) => {\n reject(err);\n });\n })\n } else {\n return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`));\n }\n } else if(message.type == 'BROADCAST_EVAL') {\n return Promise.all(this.clients.values().filter(c => c.status == 'running').map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data,\n }, 5000);\n }));\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n stopInstance(): void {\n this.eventManager?.send({\n type: 'INSTANCE_STOP'\n });\n }\n\n}","import {BotInstance} from \"./BotInstance\";\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport class StandaloneInstance extends BotInstance {\n private readonly totalClusters: number;\n private readonly shardsPerCluster: number;\n\n public readonly token: string;\n public readonly intents: GatewayIntentsString[];\n\n constructor(entryPoint: string, shardsPerCluster: number, totalClusters: number, token: string, intents: GatewayIntentsString[], execArgv?: string[]) {\n super(entryPoint, execArgv);\n this.shardsPerCluster = shardsPerCluster;\n this.totalClusters = totalClusters;\n this.token = token;\n this.intents = intents;\n }\n\n get totalShards(): number {\n return this.shardsPerCluster * this.totalClusters;\n }\n\n private calculateClusters(): Record {\n const clusters: Record = {};\n for (let i = 0; i < this.totalClusters; i++) {\n clusters[i] = [];\n for (let j = 0; j < this.shardsPerCluster; j++) {\n clusters[i].push(i * this.shardsPerCluster + j);\n }\n }\n return clusters;\n }\n\n public start(): void {\n const clusters = this.calculateClusters();\n for (const [id, shardList] of Object.entries(clusters)) {\n this.startProcess(1, Number(id), shardList, this.totalShards, this.token, this.intents);\n }\n }\n\n protected setClusterStopped(client: ClusterProcess, reason: string): void {\n this.clients.delete(client.id);\n this.restartProcess(client);\n }\n\n protected setClusterReady(client: ClusterProcess): void {\n \n }\n\n protected setClusterSpawned(client: ClusterProcess): void {\n\n }\n\n private restartProcess(client: ClusterProcess): void {\n this.startProcess(1, client.id, client.shardList, this.totalShards, this.token, this.intents);\n }\n\n protected onRequest(client: ClusterProcess, message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = message.guildID;\n const data = message.data;\n\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if(client.shardList.includes(shardID)) {\n return client.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return Promise.reject(new Error(`Shard ID ${shardID} not found in cluster ${client.id} for guild ${guildID}`));\n }\n }\n\n if(message.type == 'BROADCAST_EVAL') {\n return Promise.all(\n this.clients.values().map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data,\n }, 5000);\n })\n );\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(client, message.data, resolve, reject);\n });\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n}"],"mappings":"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","names":["BridgeClientClusterConnectionStatus","result","BridgeClientConnectionStatus","message","m","message","message","m","os","fn","message","message","import_net_ipc","BridgeConnectionStatus","message","m","client","message"]} \ No newline at end of file diff --git a/dist/index.mjs b/dist/index.mjs deleted file mode 100644 index bdf0a01..0000000 --- a/dist/index.mjs +++ /dev/null @@ -1,1526 +0,0 @@ -// src/bridge/BridgeClientCluster.ts -var BridgeClientClusterConnectionStatus = /* @__PURE__ */ ((BridgeClientClusterConnectionStatus2) => { - BridgeClientClusterConnectionStatus2["REQUESTING"] = "requesting"; - BridgeClientClusterConnectionStatus2["STARTING"] = "starting"; - BridgeClientClusterConnectionStatus2["CONNECTED"] = "connected"; - BridgeClientClusterConnectionStatus2["RECLUSTERING"] = "reclustering"; - BridgeClientClusterConnectionStatus2["DISCONNECTED"] = "disconnected"; - return BridgeClientClusterConnectionStatus2; -})(BridgeClientClusterConnectionStatus || {}); -var BridgeClientCluster = class { - clusterID; - shardList; - connectionStatus = "disconnected" /* DISCONNECTED */; - connection; - oldConnection; - missedHeartbeats = 0; - heartbeatResponse; - heartbeatPending = false; - startedAt; - constructor(clusterID, shardList) { - this.clusterID = clusterID; - this.shardList = shardList; - } - setConnection(connection) { - if (connection == void 0) { - this.connectionStatus = "disconnected" /* DISCONNECTED */; - this.connection = void 0; - return; - } - if (this.connection) { - throw new Error(`Connection already set for cluster ${this.clusterID}`); - } - this.connectionStatus = "requesting" /* REQUESTING */; - this.connection = connection; - } - setOldConnection(connection) { - this.oldConnection = connection; - } - isUsed() { - return this.connection != void 0 && this.connectionStatus !== "disconnected" /* DISCONNECTED */; - } - reclustering(connection) { - this.connectionStatus = "reclustering" /* RECLUSTERING */; - this.oldConnection = this.connection; - this.connection = connection; - } - addMissedHeartbeat() { - this.missedHeartbeats++; - } - removeMissedHeartbeat() { - if (this.missedHeartbeats > 0) { - this.missedHeartbeats--; - } - } - resetMissedHeartbeats() { - this.missedHeartbeats = 0; - } -}; - -// src/general/EventManager.ts -var EventManager = class { - pendingPayloads = /* @__PURE__ */ new Map(); - // Track per-request timeout handles so we can clear them on resolve/reject - pendingTimeouts = /* @__PURE__ */ new Map(); - _send; - _on; - _request; - constructor(send, on, request) { - this._send = send; - this._on = on; - this._request = request; - } - async send(data) { - return this._send({ - id: crypto.randomUUID(), - type: "message", - data - }); - } - async request(payload, timeout) { - const id = crypto.randomUUID(); - return new Promise((resolve, reject) => { - this._send({ - id, - type: "request", - data: payload - }); - this.pendingPayloads.set(id, { - resolve, - reject - }); - const t = setTimeout(() => { - if (this.pendingPayloads.has(id)) { - this.pendingPayloads.delete(id); - this.pendingTimeouts.delete(id); - reject({ - error: `Request with id ${id} timed out` - }); - } - }, timeout); - this.pendingTimeouts.set(id, t); - }); - } - receive(possiblePayload) { - if (typeof possiblePayload !== "object" || possiblePayload === null) { - return; - } - const payload = possiblePayload; - if (!payload.id || !payload.type) { - return; - } - if (payload.type === "message") { - this._on(payload.data); - return; - } - if (payload.type === "response") { - const resolve = this.pendingPayloads.get(payload.id)?.resolve; - if (resolve) { - resolve(payload.data); - this.pendingPayloads.delete(payload.id); - const to = this.pendingTimeouts.get(payload.id); - if (to) clearTimeout(to); - this.pendingTimeouts.delete(payload.id); - } - return; - } - if (payload.type === "response_error") { - const reject = this.pendingPayloads.get(payload.id)?.reject; - if (reject) { - reject(payload.data); - this.pendingPayloads.delete(payload.id); - const to = this.pendingTimeouts.get(payload.id); - if (to) clearTimeout(to); - this.pendingTimeouts.delete(payload.id); - } - return; - } - if (payload.type === "request") { - const data = this._request(payload.data); - if (data instanceof Promise) { - data.then((result2) => { - this._send({ - id: payload.id, - type: "response", - data: result2 - }); - }).catch((error) => { - this._send({ - id: payload.id, - type: "response_error", - data: error - }); - }); - } else { - this._send({ - id: payload.id, - type: "response", - data - }); - } - return; - } - } - // Reject and clear all pending requests to avoid memory leaks when a connection/process closes - close(reason) { - if (this.pendingPayloads.size === 0 && this.pendingTimeouts.size === 0) return; - const err = { error: reason || "EventManager closed" }; - for (const [id, handlers] of this.pendingPayloads.entries()) { - try { - handlers.reject(err); - } catch (_) { - } - this.pendingPayloads.delete(id); - const to = this.pendingTimeouts.get(id); - if (to) clearTimeout(to); - this.pendingTimeouts.delete(id); - } - for (const to of this.pendingTimeouts.values()) { - clearTimeout(to); - } - this.pendingTimeouts.clear(); - } -}; - -// src/bridge/BridgeClientConnection.ts -var BridgeClientConnectionStatus = /* @__PURE__ */ ((BridgeClientConnectionStatus2) => { - BridgeClientConnectionStatus2["READY"] = "ready"; - BridgeClientConnectionStatus2["PENDING_STOP"] = "pending_stop"; - return BridgeClientConnectionStatus2; -})(BridgeClientConnectionStatus || {}); -var BridgeClientConnection = class { - instanceID; - eventManager; - connection; - data; - connectionStatus = "ready" /* READY */; - dev = false; - establishedAt = Date.now(); - _onMessage; - _onRequest; - constructor(instanceID, connection, data, dev) { - this.instanceID = instanceID; - this.connection = connection; - this.data = data; - this.dev = dev || false; - this.eventManager = new EventManager((message2) => { - if (!this.connection?.connection?.closed) { - return this.connection.send(message2); - } - return Promise.reject(new Error("Connection is closed, cannot send message")); - }, (message2) => { - if (this._onMessage) { - this._onMessage(message2); - } - }, (message2) => { - if (this._onRequest) { - return this._onRequest(message2); - } - return void 0; - }); - } - messageReceive(message2) { - this.eventManager.receive(message2); - } - onRequest(callback) { - this._onRequest = callback; - } - onMessage(callback) { - this._onMessage = callback; - } -}; - -// src/bridge/Bridge.ts -import { Server } from "net-ipc"; - -// src/bridge/ClusterCalculator.ts -var ClusterCalculator = class { - /** The total number of clusters to initialize */ - clusterToStart; - /** The number of shards that each cluster will manage */ - shardsPerCluster; - /** List of all clusters managed by this calculator */ - clusterList = []; - /** - * Creates a new ClusterCalculator and initializes the clusters. - * - * @param clusterToStart - The number of clusters to create - * @param shardsPerCluster - The number of shards each cluster will manage - */ - constructor(clusterToStart, shardsPerCluster) { - this.shardsPerCluster = shardsPerCluster; - this.clusterToStart = clusterToStart; - this.calculateClusters(); - } - /** - * Calculates and initializes all clusters with their assigned shards. - * Each cluster is assigned a sequential range of shard IDs based on its cluster index. - */ - calculateClusters() { - const clusters = /* @__PURE__ */ new Map(); - for (let i = 0; i < this.clusterToStart; i++) { - clusters.set(i, []); - for (let j = 0; j < this.shardsPerCluster; j++) { - clusters.get(i)?.push(i * this.shardsPerCluster + j); - } - } - for (let [clusterIndex, clusterShards] of clusters.entries()) { - this.clusterList.push(new BridgeClientCluster(clusterIndex, clusterShards)); - } - } - /** - * Retrieves the next available (unused) cluster and marks it as used. - * - * @returns The next available cluster, or undefined if all clusters are in use - */ - getNextCluster() { - for (const cluster of this.clusterList) { - if (!cluster.isUsed()) { - return cluster; - } - } - return void 0; - } - /** - * Retrieves multiple available clusters up to the specified count. - * Each returned cluster is marked as used. - * - * @param count - The maximum number of clusters to retrieve - * @returns An array of available clusters (may be fewer than requested if not enough are available) - */ - getNextClusters(count) { - const availableClusters = []; - for (const cluster of this.clusterList) { - if (!cluster.isUsed() && availableClusters.length < count) { - availableClusters.push(cluster); - } - } - return availableClusters; - } - /** - * Sets the used status of a specific cluster by its ID. - * - * @param clusterID - The ID of the cluster to update - * @param connection - The connection to associate with the cluster - */ - clearClusterConnection(clusterID) { - const cluster = this.clusterList.find((c) => c.clusterID === clusterID); - if (cluster) { - cluster.setConnection(void 0); - } - } - getClusterForConnection(connection) { - return this.clusterList.filter( - (cluster) => cluster.connection?.instanceID === connection.instanceID - ); - } - getOldClusterForConnection(connection) { - return this.clusterList.filter( - (cluster) => cluster.oldConnection?.instanceID === connection.instanceID - ); - } - checkAllClustersConnected() { - for (const cluster of this.clusterList) { - if (cluster.connectionStatus != "connected" /* CONNECTED */) { - return false; - } - } - return true; - } - findMostAndLeastClustersForConnections(connectedClients) { - const openClients = connectedClients.filter((x) => !x.dev); - const devClients = connectedClients.filter((x) => x.dev); - const summDevConnectedClusters = devClients.map((c) => this.getClusterForConnection(c).length).reduce((a, b) => a + b, 0); - let most; - let least; - let remainder = (this.clusterToStart - summDevConnectedClusters) % openClients.length || 0; - for (const client of openClients) { - const clusters = this.getClusterForConnection(client); - if (!most || clusters.length > this.getClusterForConnection(most).length) { - most = client; - } - if (!least || clusters.length < this.getClusterForConnection(least).length) { - least = client; - } - } - if (most && least) { - const mostCount = this.getClusterForConnection(most).length; - const leastCount = this.getClusterForConnection(least).length; - if (mostCount - leastCount <= remainder) { - return { most: void 0, least: void 0 }; - } - } - return { most, least }; - } - getClusterWithLowestLoad(connectedClients) { - let lowestLoadClient; - let lowestLoad = Infinity; - for (const client of connectedClients.values().filter((c) => c.connectionStatus === "ready" /* READY */ && !c.dev)) { - const clusters = this.getClusterForConnection(client); - const load = clusters.length; - if (load < lowestLoad) { - lowestLoad = load; - lowestLoadClient = client; - } - } - return lowestLoadClient; - } - getClusterOfShard(shardID) { - return this.clusterList.find((c) => c.shardList.includes(shardID)); - } -}; - -// src/general/ShardingUtil.ts -var ShardingUtil = class { - static getShardIDForGuild(guildID, totalShards) { - if (!guildID || totalShards <= 0) { - throw new Error("Invalid guild ID or total shards"); - } - return Number(BigInt(guildID) >> 22n) % totalShards; - } -}; - -// src/bridge/Bridge.ts -var Bridge = class { - port; - server; - connectedClients = /* @__PURE__ */ new Map(); - token; - intents; - shardsPerCluster = 1; - clusterToStart = 1; - reclusteringTimeoutInMs; - clusterCalculator; - eventMap = { - CLUSTER_READY: void 0, - CLUSTER_HEARTBEAT_FAILED: void 0, - CLUSTER_STOPPED: void 0, - CLIENT_CONNECTED: void 0, - CLIENT_DISCONNECTED: void 0, - CLUSTER_SPAWNED: void 0, - CLUSTER_RECLUSTER: void 0, - ERROR: void 0, - CLIENT_STOP: void 0 - }; - constructor(port, token, intents, shardsPerCluster, clusterToStart, reclusteringTimeoutInMs) { - this.port = port; - this.token = token; - this.intents = intents; - this.clusterToStart = clusterToStart; - this.shardsPerCluster = shardsPerCluster; - this.reclusteringTimeoutInMs = reclusteringTimeoutInMs; - this.clusterCalculator = new ClusterCalculator(this.clusterToStart, this.shardsPerCluster); - this.server = new Server({ - port: this.port - }); - } - start() { - this.server.start().then(() => { - this.startListening(); - }); - this.interval(); - } - interval() { - setInterval(() => { - this.checkCreate(); - this.checkRecluster(); - this.heartbeat(); - }, 5e3); - } - checkRecluster() { - const up = this.clusterCalculator.checkAllClustersConnected(); - if (!up) { - return; - } - const connectedClients = this.connectedClients.values().filter((c) => c.connectionStatus == "ready" /* READY */).filter((c) => !c.dev).filter((c) => c.establishedAt + this.reclusteringTimeoutInMs < Date.now()).toArray(); - const { most, least } = this.clusterCalculator.findMostAndLeastClustersForConnections(connectedClients); - if (most) { - const clusterToSteal = this.clusterCalculator.getClusterForConnection(most)[0] || void 0; - if (least && clusterToSteal) { - clusterToSteal.reclustering(least); - if (this.eventMap.CLUSTER_RECLUSTER) this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection); - this.createCluster(least, clusterToSteal, true); - return; - } - } - } - heartbeat() { - const clusters = this.clusterCalculator.clusterList; - clusters.forEach((cluster) => { - if (cluster.connection && cluster.connectionStatus == "connected" /* CONNECTED */ && !cluster.heartbeatPending) { - cluster.heartbeatPending = true; - cluster.connection.eventManager.request({ - type: "CLUSTER_HEARTBEAT", - data: { - clusterID: cluster.clusterID - } - }, 2e4).then((r) => { - cluster.removeMissedHeartbeat(); - cluster.heartbeatResponse = r; - }).catch((err) => { - if (this.eventMap.CLUSTER_HEARTBEAT_FAILED) this.eventMap.CLUSTER_HEARTBEAT_FAILED(cluster, err); - cluster.addMissedHeartbeat(); - if (cluster.missedHeartbeats > 7 && !cluster.connection?.dev) { - cluster.connection?.eventManager.send({ - type: "CLUSTER_STOP", - data: { - id: cluster.clusterID - } - }); - cluster.connectionStatus = "disconnected" /* DISCONNECTED */; - cluster.resetMissedHeartbeats(); - } - }).finally(() => { - cluster.heartbeatPending = false; - }); - } - }); - } - checkCreate() { - const optionalCluster = this.clusterCalculator.getNextCluster(); - if (!optionalCluster) { - return; - } - const lowestLoadClient = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients); - if (!lowestLoadClient) { - return; - } - this.createCluster(lowestLoadClient, optionalCluster); - } - createCluster(connection, cluster, recluster = false) { - cluster.resetMissedHeartbeats(); - cluster.heartbeatResponse = void 0; - if (!recluster) { - cluster.setConnection(connection); - } else { - cluster.oldConnection?.eventManager.send({ - type: "CLUSTER_RECLUSTER", - data: { - clusterID: cluster.clusterID - } - }); - } - if (this.eventMap.CLUSTER_SPAWNED) this.eventMap.CLUSTER_SPAWNED(cluster, connection); - connection.eventManager.send({ - type: "CLUSTER_CREATE", - data: { - clusterID: cluster.clusterID, - instanceID: connection.instanceID, - totalShards: this.getTotalShards(), - shardList: cluster.shardList, - token: this.token, - intents: this.intents - } - }); - } - startListening() { - this.server.on("connect", (connection, payload) => { - const id = payload?.id; - const data = payload.data; - const dev = payload?.dev || false; - if (!id) { - connection.close("Invalid payload", false); - return; - } - if (this.connectedClients.values().some((client) => client.instanceID === id)) { - connection.close("Already connected", false); - return; - } - const bridgeConnection = new BridgeClientConnection(payload.id, connection, data, dev); - if (this.eventMap.CLIENT_CONNECTED) this.eventMap.CLIENT_CONNECTED(bridgeConnection); - bridgeConnection.onMessage((m2) => { - if (m2.type == "CLUSTER_SPAWNED") { - const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); - if (cluster) { - cluster.connectionStatus = "starting" /* STARTING */; - } - return; - } - if (m2.type == "CLUSTER_READY") { - const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); - if (cluster) { - cluster.startedAt = Date.now(); - if (this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(cluster, m2.data.guilds || 0, m2.data.members || 0); - cluster.connectionStatus = "connected" /* CONNECTED */; - if (cluster.oldConnection) { - cluster.oldConnection.eventManager.send({ - type: "CLUSTER_STOP", - data: { - id: cluster.clusterID - } - }); - cluster.oldConnection = void 0; - } - } - return; - } - if (m2.type == "CLUSTER_STOPPED") { - const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id); - if (cluster) { - cluster.startedAt = void 0; - if (this.eventMap.CLUSTER_STOPPED) this.eventMap.CLUSTER_STOPPED(cluster); - cluster.setConnection(void 0); - } - return; - } - if (m2.type == "INSTANCE_STOP") { - this.stopInstance(bridgeConnection); - } - return; - }); - bridgeConnection.onRequest((m2) => { - if (m2.type == "REDIRECT_REQUEST_TO_GUILD") { - const guildID = m2.guildID; - const shardID = ShardingUtil.getShardIDForGuild(guildID, this.getTotalShards()); - const cluster = this.clusterCalculator.getClusterOfShard(shardID); - if (!cluster) { - return Promise.reject(new Error("cluster not found")); - } - if (cluster.connectionStatus != "connected" /* CONNECTED */) { - return Promise.reject(new Error("cluster not connected.")); - } - if (!cluster.connection?.eventManager) { - return Promise.reject(new Error("no connection defined.")); - } - return cluster.connection.eventManager.request({ - type: "REDIRECT_REQUEST_TO_GUILD", - clusterID: cluster.clusterID, - guildID, - data: m2.data - }, 5e3); - } - if (m2.type == "BROADCAST_EVAL") { - const responses = Promise.all( - this.connectedClients.values().map((c) => { - return c.eventManager.request({ - type: "BROADCAST_EVAL", - data: m2.data - }, 5e3); - }) - ); - return new Promise((resolve, reject) => { - responses.then((r) => { - resolve(r.flatMap((f) => f)); - }).catch(reject); - }); - } - if (m2.type == "SELF_CHECK") { - return { - clusterList: [ - ...this.clusterCalculator.getClusterForConnection(bridgeConnection).map((c) => c.clusterID), - ...this.clusterCalculator.getOldClusterForConnection(bridgeConnection).map((c) => c.clusterID) - ] - }; - } - return Promise.reject(new Error("unknown type")); - }); - this.connectedClients.set(connection.id, bridgeConnection); - }); - this.server.on("disconnect", (connection, reason) => { - const closedConnection = this.connectedClients.get(connection.id); - if (!closedConnection) { - return; - } - const clusters = this.clusterCalculator.getClusterForConnection(closedConnection); - for (const cluster of clusters) { - this.clusterCalculator.clearClusterConnection(cluster.clusterID); - } - this.connectedClients.delete(connection.id); - if (this.eventMap.CLIENT_DISCONNECTED) this.eventMap.CLIENT_DISCONNECTED(closedConnection, reason); - }); - this.server.on("message", (message2, connection) => { - this.sendMessageToClient(connection.id, message2); - }); - } - sendMessageToClient(clientId, message2) { - if (!this.connectedClients.has(clientId)) { - return; - } - const client = this.connectedClients.get(clientId); - if (client) { - client.messageReceive(message2); - } - } - getTotalShards() { - return this.shardsPerCluster * this.clusterToStart; - } - on(event, listener) { - this.eventMap[event] = listener; - } - getClusters() { - return this.clusterCalculator.clusterList; - } - async stopAllInstances() { - const instances = Array.from(this.connectedClients.values()); - for (const instance of instances) { - instance.connectionStatus = "pending_stop" /* PENDING_STOP */; - } - for (const instance of instances) { - await this.stopInstance(instance, false); - } - } - async stopAllInstancesWithRestart() { - const instances = Array.from(this.connectedClients.values()); - for (const instance of instances) { - await this.stopInstance(instance); - await new Promise((resolve) => { - setTimeout(async () => { - resolve(); - }, 1e3 * 10); - }); - } - } - async moveCluster(instance, cluster) { - cluster.reclustering(instance); - this.createCluster(instance, cluster, true); - } - async stopInstance(instance, recluster = true) { - if (this.eventMap.CLIENT_STOP) this.eventMap.CLIENT_STOP(instance); - instance.connectionStatus = "pending_stop" /* PENDING_STOP */; - let clusterToSteal; - await instance.eventManager.send({ - type: "INSTANCE_STOP" - }); - if (recluster) { - while ((clusterToSteal = this.clusterCalculator.getClusterForConnection(instance).filter((c) => c.connectionStatus === "connected" /* CONNECTED */ || c.connectionStatus == "starting" /* STARTING */ || c.connectionStatus == "reclustering" /* RECLUSTERING */)[0]) !== void 0) { - if (clusterToSteal.connectionStatus != "connected" /* CONNECTED */) break; - const least = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients); - if (!least) { - if (this.eventMap.ERROR) { - this.eventMap.ERROR("Reclustering failed: No least cluster found."); - } - await instance.eventManager.send({ - type: "CLUSTER_STOP", - data: { - id: clusterToSteal.clusterID - } - }); - clusterToSteal.connection = void 0; - clusterToSteal.connectionStatus = "disconnected" /* DISCONNECTED */; - continue; - } - clusterToSteal.reclustering(least); - if (this.eventMap.CLUSTER_RECLUSTER) { - this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection); - } - this.createCluster(least, clusterToSteal, true); - } - return new Promise((resolve, reject) => { - const interval = setInterval(async () => { - const cluster = this.clusterCalculator.getOldClusterForConnection(instance)[0] || void 0; - if (!cluster) { - clearInterval(interval); - await instance.eventManager.send({ - type: "INSTANCE_STOPPED" - }); - await instance.connection.close("Instance stopped.", false); - resolve(); - return; - } - }, 1e3); - }); - } else { - for (const cluster of this.clusterCalculator.getClusterForConnection(instance)) { - await instance.eventManager.send({ - type: "CLUSTER_STOP", - data: { - id: cluster.clusterID - } - }); - } - await instance.eventManager.send({ - type: "INSTANCE_STOPPED" - }); - await instance.connection.close("Instance stopped.", false); - } - } - sendRequestToGuild(cluster, guildID, data, timeout = 5e3) { - if (!cluster.connection) { - return Promise.reject(new Error("No connection defined for cluster " + cluster.clusterID)); - } - return cluster.connection.eventManager.request({ - type: "REDIRECT_REQUEST_TO_GUILD", - clusterID: cluster.clusterID, - guildID, - data - }, timeout); - } -}; - -// src/cluster/Cluster.ts -import os from "os"; -var Cluster = class _Cluster { - instanceID; - clusterID; - shardList = []; - totalShards; - token; - intents; - eventManager; - client; - onSelfDestruct; - eventMap = { - message: void 0, - request: void 0, - CLUSTER_READY: void 0 - }; - constructor(instanceID, clusterID, shardList, totalShards, token, intents) { - this.instanceID = instanceID; - this.clusterID = clusterID; - this.shardList = shardList; - this.totalShards = totalShards; - this.token = token; - this.intents = intents; - this.eventManager = new EventManager((message2) => { - return new Promise((resolve, reject) => { - if (typeof process.send !== "function") { - reject(new Error("Process does not support sending messages")); - return; - } - process.send?.(message2, void 0, void 0, (error) => { - if (error) { - reject(error); - } else { - resolve(); - } - }); - }); - }, (message2) => { - this._onMessage(message2); - }, (message2) => { - return this._onRequest(message2); - }); - process.on("message", (message2) => { - this.eventManager.receive(message2); - }); - } - static initial() { - const args = process.env; - if (args.SHARD_LIST == void 0 || args.INSTANCE_ID == void 0 || args.TOTAL_SHARDS == void 0 || args.TOKEN == void 0 || args.INTENTS == void 0 || args.CLUSTER_ID == void 0) { - throw new Error("Missing required environment variables"); - } - const shardList = args.SHARD_LIST.split(",").map(Number); - const totalShards = Number(args.TOTAL_SHARDS); - const instanceID = Number(args.INSTANCE_ID); - const clusterID = Number(args.CLUSTER_ID); - const token = args.TOKEN; - const intents = args.INTENTS.split(",").map((i) => i.trim()); - return new _Cluster(instanceID, clusterID, shardList, totalShards, token, intents); - } - triggerReady(guilds, members) { - this.eventManager.send({ - type: "CLUSTER_READY", - id: this.clusterID, - guilds, - members - }); - if (this.eventMap?.CLUSTER_READY) { - this.eventMap?.CLUSTER_READY(); - } - } - triggerError(e) { - this.eventManager.send({ - type: "CLUSTER_ERROR", - id: this.clusterID - }); - } - async wait(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - _onMessage(message2) { - const m2 = message2; - if (m2.type == "CUSTOM" && this.eventMap.message) { - this.eventMap.message(m2.data); - } - } - _onRequest(message) { - const m = message; - if (m.type == "CUSTOM" && this.eventMap.request) { - return new Promise((resolve, reject) => { - this.eventMap.request(m.data, resolve, reject); - }); - } else if (m.type == "CLUSTER_HEARTBEAT") { - const startTime = process.hrtime.bigint(); - const startUsage = process.cpuUsage(); - (async () => { - await this.wait(500); - })(); - const endTime = process.hrtime.bigint(); - const usageDiff = process.cpuUsage(startUsage); - const elapsedTimeUs = Number((endTime - startTime) / 1000n); - const totalCPUTime = usageDiff.user + usageDiff.system; - const cpuCount = os.cpus().length; - const cpuPercent = totalCPUTime / (elapsedTimeUs * cpuCount) * 100; - let shardPings = []; - try { - const shards = this.client.ws.shards; - if (shards) { - shards.forEach((shard) => { - shardPings.push({ - id: shard.id, - ping: shard.ping, - status: shard.status, - guilds: this.client.guilds.cache.filter((g) => g.shardId === shard.id).size, - members: this.client.guilds.cache.filter((g) => g.shardId === shard.id).reduce((acc, g) => acc + g.memberCount, 0) - }); - this.client.shard?.fetchClientValues("uptime", shard.id).then((values) => { - shardPings[shard.id]["uptime"] = values; - console.log(values); - }).catch((e) => { - }); - }); - } - } catch (_) { - } - return { - cpu: { raw: process.cpuUsage(), cpuPercent: cpuPercent.toFixed(2) }, - memory: { - raw: process.memoryUsage(), - memoryPercent: (process.memoryUsage().heapUsed / process.memoryUsage().heapTotal * 100).toFixed(2) + "%", - usage: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + "MB" - }, - ping: this.client.ws.ping, - shardPings - }; - } else if (m.type == "BROADCAST_EVAL") { - const broadcast = message; - const fn = eval(`(${broadcast.data})`); - const result = fn(this.client); - if (result instanceof Promise) { - return new Promise((resolve, reject) => { - result.then((res) => { - resolve(res); - }).catch((err) => { - reject(err); - }); - }); - } else { - return result; - } - } else if (m.type == "SELF_DESTRUCT") { - if (this.onSelfDestruct) { - this.onSelfDestruct(); - } - } - return void 0; - } - on(event, listener) { - this.eventMap[event] = listener; - } - sendMessage(data) { - this.eventManager.send({ - type: "CUSTOM", - data - }); - } - sendRequest(data, timeout = 5e3) { - return this.eventManager.request({ - type: "CUSTOM", - data - }, timeout); - } - broadcastEval(fn2, timeout = 2e4) { - return this.eventManager.request({ - type: "BROADCAST_EVAL", - data: fn2.toString() - }, timeout); - } - sendMessageToClusterOfGuild(guildID, message2) { - if (this.eventManager) { - this.eventManager.send({ - type: "REDIRECT_MESSAGE_TO_GUILD", - guildID, - data: message2 - }); - } - } - sendRequestToClusterOfGuild(guildID, message2, timeout = 5e3) { - return new Promise((resolve, reject) => { - if (this.eventManager) { - this.eventManager.request({ - type: "REDIRECT_REQUEST_TO_GUILD", - guildID, - data: message2 - }, timeout).then((response) => { - resolve(response); - }).catch((error) => { - reject(error); - }); - } else { - reject(new Error("Event manager is not initialized")); - } - }); - } -}; - -// src/cluster/ClusterProcess.ts -var ClusterProcess = class { - child; - eventManager; - id; - shardList; - totalShards; - status; - createdAt = Date.now(); - _onMessage; - _onRequest; - constructor(id, child, shardList, totalShards) { - this.id = id; - this.child = child; - this.shardList = shardList; - this.totalShards = totalShards; - this.status = "starting"; - this.eventManager = new EventManager((message2) => { - return new Promise((resolve, reject) => { - this.child.send(message2, (error) => { - if (error) { - reject(error); - } else { - resolve(); - } - }); - }); - }, (message2) => { - if (this._onMessage) { - this._onMessage(message2); - } - }, (message2) => { - if (this._onRequest) { - return this._onRequest(message2); - } - return void 0; - }); - this.child.on("message", (message2) => { - this.eventManager.receive(message2); - }); - this.child.on("exit", () => { - this.eventManager.close("child process exited"); - }); - this.child.on("error", () => { - this.eventManager.close("child process error"); - }); - } - onMessage(callback) { - this._onMessage = callback; - } - onRequest(callback) { - this._onRequest = callback; - } - sendMessage(data) { - this.eventManager.send({ - type: "CUSTOM", - data - }); - } - sendRequest(data, timeout = 5e3) { - return this.eventManager.request({ - type: "CUSTOM", - data - }, timeout); - } -}; - -// src/instance/BotInstance.ts -import { fork } from "child_process"; -var BotInstance = class { - entryPoint; - execArgv; - clients = /* @__PURE__ */ new Map(); - constructor(entryPoint, execArgv) { - this.entryPoint = entryPoint; - this.execArgv = execArgv ?? []; - } - eventMap = { - "message": void 0, - "request": void 0, - "PROCESS_KILLED": void 0, - "PROCESS_SELF_DESTRUCT_ERROR": void 0, - "PROCESS_SPAWNED": void 0, - "ERROR": void 0, - "PROCESS_ERROR": void 0, - "CLUSTER_READY": void 0, - "CLUSTER_ERROR": void 0, - "CLUSTER_RECLUSTER": void 0, - "BRIDGE_CONNECTION_ESTABLISHED": void 0, - "BRIDGE_CONNECTION_CLOSED": void 0, - "BRIDGE_CONNECTION_STATUS_CHANGE": void 0, - "INSTANCE_STOP": void 0, - "INSTANCE_STOPPED": void 0, - "SELF_CHECK_SUCCESS": void 0, - "SELF_CHECK_ERROR": void 0, - "SELF_CHECK_RECEIVED": void 0 - }; - startProcess(instanceID, clusterID, shardList, totalShards, token, intents) { - try { - const child = fork(this.entryPoint, { - env: { - INSTANCE_ID: instanceID.toString(), - CLUSTER_ID: clusterID.toString(), - SHARD_LIST: shardList.join(","), - TOTAL_SHARDS: totalShards.toString(), - TOKEN: token, - INTENTS: intents.join(","), - FORCE_COLOR: "true" - }, - stdio: "inherit", - execArgv: this.execArgv, - silent: false, - detached: true - }); - const client = new ClusterProcess(clusterID, child, shardList, totalShards); - child.stdout?.on("data", (data) => { - process.stdout.write(data); - }); - child.stderr?.on("data", (data) => { - process.stderr.write(data); - }); - child.on("spawn", () => { - if (this.eventMap.PROCESS_SPAWNED) this.eventMap.PROCESS_SPAWNED(client); - this.setClusterSpawned(client); - this.clients.set(clusterID, client); - client.onMessage((message2) => { - this.onMessage(client, message2); - }); - client.onRequest((message2) => { - return this.onRequest(client, message2); - }); - }); - child.on("error", (err) => { - if (this.eventMap.PROCESS_ERROR) this.eventMap.PROCESS_ERROR(client, err); - }); - child.on("exit", (err) => { - if (client.status !== "stopped") { - client.status = "stopped"; - this.killProcess(client, `Process exited: ${err?.message}`); - } - }); - } catch (error) { - throw new Error(`Failed to start process for cluster ${clusterID}: ${error instanceof Error ? error.message : String(error)}`); - } - } - killProcess(client, reason) { - client.status = "stopped"; - client.eventManager.request({ - type: "SELF_DESTRUCT", - reason - }, 5e3).catch(() => { - if (this.eventMap.PROCESS_SELF_DESTRUCT_ERROR) this.eventMap.PROCESS_SELF_DESTRUCT_ERROR(client, reason, "Cluster didnt respond to shot-call."); - }).finally(() => { - if (client.child && client.child.pid) { - if (client.child.kill("SIGKILL")) { - if (this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, true); - } else { - if (this.eventMap.ERROR) this.eventMap.ERROR(`Failed to kill process for cluster ${client.id}`); - client.child.kill("SIGKILL"); - } - try { - process.kill(-client.child.pid); - } catch { - } - } else { - if (this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, false); - } - this.clients.delete(client.id); - this.setClusterStopped(client, reason); - }); - } - onMessage(client, message2) { - if (message2.type === "CLUSTER_READY") { - client.status = "running"; - if (this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(client); - this.setClusterReady(client, message2.guilds || 0, message2.members || 0); - } - if (message2.type === "CLUSTER_ERROR") { - client.status = "stopped"; - if (this.eventMap.CLUSTER_ERROR) this.eventMap.CLUSTER_ERROR(client, message2.error); - this.killProcess(client, "Cluster error: " + message2.error); - } - if (message2.type == "CUSTOM" && this.eventMap.message) { - this.eventMap.message(client, message2.data); - } - } - on(event, listener) { - this.eventMap[event] = listener; - } - sendRequestToClusterOfGuild(guildID, message2, timeout = 5e3) { - return new Promise((resolve, reject) => { - for (const client of this.clients.values()) { - const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); - if (client.shardList.includes(shardID)) { - client.eventManager.request({ - type: "CUSTOM", - data: message2 - }, timeout).then(resolve).catch(reject); - return; - } - } - reject(new Error(`No cluster found for guild ${guildID}`)); - }); - } - sendRequestToCluster(cluster, message2, timeout = 5e3) { - return new Promise((resolve, reject) => { - cluster.eventManager.request({ - type: "CUSTOM", - data: message2 - }, timeout).then(resolve).catch(reject); - return; - }); - } -}; - -// src/instance/ManagedInstance.ts -import { Client } from "net-ipc"; -var BridgeConnectionStatus = /* @__PURE__ */ ((BridgeConnectionStatus2) => { - BridgeConnectionStatus2[BridgeConnectionStatus2["CONNECTED"] = 0] = "CONNECTED"; - BridgeConnectionStatus2[BridgeConnectionStatus2["DISCONNECTED"] = 1] = "DISCONNECTED"; - return BridgeConnectionStatus2; -})(BridgeConnectionStatus || {}); -var ManagedInstance = class extends BotInstance { - host; - port; - instanceID; - eventManager; - connectionStatus = 1 /* DISCONNECTED */; - data; - dev = false; - constructor(entryPoint, host, port, instanceID, data, execArgv, dev) { - super(entryPoint, execArgv); - this.host = host; - this.port = port; - this.instanceID = instanceID; - this.data = data; - this.dev = dev || false; - } - start() { - const client = new Client({ - host: this.host, - port: this.port, - reconnect: true, - retries: 100 - }); - this.eventManager = new EventManager((message2) => { - if (client.status == 3) { - return client.send(message2); - } - return Promise.reject(new Error("Client is not ready to send messages")); - }, (message2) => { - const m2 = message2; - if (m2.type == "CLUSTER_CREATE") { - this.onClusterCreate(m2.data); - } else if (m2.type == "CLUSTER_STOP") { - this.onClusterStop(m2.data); - } else if (m2.type == "CLUSTER_RECLUSTER") { - this.onClusterRecluster(m2.data); - } else if (m2.type == "INSTANCE_STOP") { - if (this.eventMap.INSTANCE_STOP) { - this.eventMap.INSTANCE_STOP(); - } - } else if (m2.type == "INSTANCE_STOPPED") { - if (this.eventMap.INSTANCE_STOPPED) { - this.eventMap.INSTANCE_STOPPED(); - } - } - }, (message2) => { - return this.onBridgeRequest(message2); - }); - setInterval(() => { - if (this.connectionStatus == 0 /* CONNECTED */) { - this.selfCheck(); - } - }, 2500); - client.connect({ - id: this.instanceID, - dev: this.dev, - data: this.data - }).then((_) => { - if (this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED(); - this.connectionStatus = 0 /* CONNECTED */; - client.on("message", (message2) => { - this.eventManager?.receive(message2); - }); - client.on("close", (reason) => { - if (this.eventMap.BRIDGE_CONNECTION_CLOSED) this.eventMap.BRIDGE_CONNECTION_CLOSED(reason); - if (this.connectionStatus == 0 /* CONNECTED */) { - this.clients.forEach((client2) => { - this.killProcess(client2, "Bridge connection closed"); - }); - } - this.connectionStatus = 1 /* DISCONNECTED */; - }); - client.on("status", (status) => { - if (this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE) this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE(status); - if (status == 4) { - if (this.connectionStatus == 0 /* CONNECTED */) { - this.clients.forEach((client2) => { - this.killProcess(client2, "Bridge connection closed"); - }); - } - this.connectionStatus = 1 /* DISCONNECTED */; - } else if (status == 3) { - this.connectionStatus = 0 /* CONNECTED */; - if (this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED(); - } - }); - }); - } - selfCheck() { - this.eventManager.request({ - type: "SELF_CHECK" - }, 1e3 * 60).then((r) => { - const response = r; - if (this.eventMap.SELF_CHECK_RECEIVED) { - this.eventMap.SELF_CHECK_RECEIVED(response); - } - const startingClusters = this.clients.values().filter((c) => c.status == "starting").toArray(); - startingClusters.forEach((c) => { - if (Date.now() - c.createdAt > 10 * 60 * 1e3) { - this.killProcess(c, "Cluster took too long to start"); - } - }); - const wrongClusters = this.clients.values().filter((c) => !response.clusterList.includes(c.id)).toArray(); - if (wrongClusters.length > 0) { - if (this.eventMap.SELF_CHECK_ERROR) { - this.eventMap.SELF_CHECK_ERROR(`Self check found wrong clusters: ${wrongClusters.map((c) => c.id).join(", ")}`); - } - wrongClusters.forEach((c) => { - this.killProcess(c, "Self check found wrong cluster"); - }); - } else { - if (this.eventMap.SELF_CHECK_SUCCESS) { - this.eventMap.SELF_CHECK_SUCCESS(); - } - } - }).catch((err) => { - if (this.eventMap.SELF_CHECK_ERROR) { - this.eventMap.SELF_CHECK_ERROR(`Self check failed: ${err}`); - } - }); - } - setClusterStopped(client, reason) { - this.eventManager?.send({ - type: "CLUSTER_STOPPED", - data: { - id: client.id, - reason - } - }).catch(() => { - return null; - }); - } - setClusterReady(client, guilds, members) { - this.eventManager?.send({ - type: "CLUSTER_READY", - data: { - id: client.id, - guilds, - members - } - }); - } - setClusterSpawned(client) { - this.eventManager?.send({ - type: "CLUSTER_SPAWNED", - data: { - id: client.id - } - }); - } - onClusterCreate(message2) { - const m2 = message2; - if (this.clients.has(m2.clusterID)) { - this.eventManager?.send({ - type: "CLUSTER_STOPPED", - data: { - id: m2.clusterID, - reason: "Cluster already exists" - } - }).catch(() => { - return null; - }); - return; - } - this.startProcess(this.instanceID, m2.clusterID, m2.shardList, m2.totalShards, m2.token, m2.intents); - } - onClusterStop(message2) { - const m2 = message2; - const cluster = this.clients.get(m2.id); - if (cluster) { - this.killProcess(cluster, `Request to stop cluster ${m2.id}`); - } - } - onClusterRecluster(message2) { - const m2 = message2; - const cluster = this.clients.get(m2.clusterID); - if (this.eventMap.CLUSTER_RECLUSTER && cluster) { - this.eventMap.CLUSTER_RECLUSTER(cluster); - } - } - onRequest(client, message2) { - if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { - const guildID = message2.guildID; - const data = message2.data; - const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); - if (client.shardList.includes(shardID)) { - return client.eventManager.request({ - type: "CUSTOM", - data - }, 5e3); - } else { - return this.eventManager.request({ - type: "REDIRECT_REQUEST_TO_GUILD", - guildID, - data - }, 5e3); - } - } - if (message2.type == "BROADCAST_EVAL") { - return this.eventManager.request({ - type: "BROADCAST_EVAL", - data: message2.data - }, 5e3); - } - if (message2.type == "CUSTOM" && this.eventMap.request) { - return new Promise((resolve, reject) => { - this.eventMap.request(client, message2.data, resolve, reject); - }); - } - return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); - } - onBridgeRequest(message2) { - if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { - const clusterID = message2.clusterID; - const data = message2.data; - const cluster = this.clients.get(clusterID); - if (cluster) { - return cluster.eventManager.request({ - type: "CUSTOM", - data - }, 5e3); - } else { - return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`)); - } - } else if (message2.type == "CLUSTER_HEARTBEAT") { - const clusterID = message2.data.clusterID; - const cluster = this.clients.get(clusterID); - if (cluster) { - return new Promise((resolve, reject) => { - cluster.eventManager.request({ - type: "CLUSTER_HEARTBEAT" - }, 15e3).then((r) => { - resolve(r); - }).catch((err) => { - reject(err); - }); - }); - } else { - return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`)); - } - } else if (message2.type == "BROADCAST_EVAL") { - return Promise.all(this.clients.values().filter((c) => c.status == "running").map((c) => { - return c.eventManager.request({ - type: "BROADCAST_EVAL", - data: message2.data - }, 5e3); - })); - } - return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); - } - stopInstance() { - this.eventManager?.send({ - type: "INSTANCE_STOP" - }); - } -}; - -// src/instance/StandaloneInstance.ts -var StandaloneInstance = class extends BotInstance { - totalClusters; - shardsPerCluster; - token; - intents; - constructor(entryPoint, shardsPerCluster, totalClusters, token, intents, execArgv) { - super(entryPoint, execArgv); - this.shardsPerCluster = shardsPerCluster; - this.totalClusters = totalClusters; - this.token = token; - this.intents = intents; - } - get totalShards() { - return this.shardsPerCluster * this.totalClusters; - } - calculateClusters() { - const clusters = {}; - for (let i = 0; i < this.totalClusters; i++) { - clusters[i] = []; - for (let j = 0; j < this.shardsPerCluster; j++) { - clusters[i].push(i * this.shardsPerCluster + j); - } - } - return clusters; - } - start() { - const clusters = this.calculateClusters(); - for (const [id, shardList] of Object.entries(clusters)) { - this.startProcess(1, Number(id), shardList, this.totalShards, this.token, this.intents); - } - } - setClusterStopped(client, reason) { - this.clients.delete(client.id); - this.restartProcess(client); - } - setClusterReady(client) { - } - setClusterSpawned(client) { - } - restartProcess(client) { - this.startProcess(1, client.id, client.shardList, this.totalShards, this.token, this.intents); - } - onRequest(client, message2) { - if (message2.type === "REDIRECT_REQUEST_TO_GUILD") { - const guildID = message2.guildID; - const data = message2.data; - const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards); - if (client.shardList.includes(shardID)) { - return client.eventManager.request({ - type: "CUSTOM", - data - }, 5e3); - } else { - return Promise.reject(new Error(`Shard ID ${shardID} not found in cluster ${client.id} for guild ${guildID}`)); - } - } - if (message2.type == "BROADCAST_EVAL") { - return Promise.all( - this.clients.values().map((c) => { - return c.eventManager.request({ - type: "BROADCAST_EVAL", - data: message2.data - }, 5e3); - }) - ); - } - if (message2.type == "CUSTOM" && this.eventMap.request) { - return new Promise((resolve, reject) => { - this.eventMap.request(client, message2.data, resolve, reject); - }); - } - return Promise.reject(new Error(`Unknown request type: ${message2.type}`)); - } -}; -export { - BotInstance, - Bridge, - BridgeClientCluster, - BridgeClientClusterConnectionStatus, - BridgeClientConnection, - BridgeClientConnectionStatus, - BridgeConnectionStatus, - Cluster, - ClusterCalculator, - ClusterProcess, - EventManager, - ManagedInstance, - ShardingUtil, - StandaloneInstance -}; -//# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/dist/index.mjs.map b/dist/index.mjs.map deleted file mode 100644 index 133deee..0000000 --- a/dist/index.mjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/bridge/BridgeClientCluster.ts","../src/general/EventManager.ts","../src/bridge/BridgeClientConnection.ts","../src/bridge/Bridge.ts","../src/bridge/ClusterCalculator.ts","../src/general/ShardingUtil.ts","../src/cluster/Cluster.ts","../src/cluster/ClusterProcess.ts","../src/instance/BotInstance.ts","../src/instance/ManagedInstance.ts","../src/instance/StandaloneInstance.ts"],"sourcesContent":["import {BridgeClientConnection} from \"./BridgeClientConnection\";\n\nexport enum BridgeClientClusterConnectionStatus {\n REQUESTING = 'requesting',\n STARTING = 'starting',\n CONNECTED = 'connected',\n RECLUSTERING = 'reclustering',\n DISCONNECTED = 'disconnected',\n}\n\nexport class BridgeClientCluster {\n public readonly clusterID: number;\n public readonly shardList: number[];\n public connectionStatus: BridgeClientClusterConnectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n\n public connection?: BridgeClientConnection;\n\n public oldConnection?: BridgeClientConnection;\n\n public missedHeartbeats: number = 0;\n\n public heartbeatResponse?: HeartbeatResponse;\n\n public heartbeatPending = false;\n\n public startedAt?: number;\n\n constructor(clusterID: number, shardList: number[]) {\n this.clusterID = clusterID;\n this.shardList = shardList;\n }\n\n setConnection(connection?: BridgeClientConnection): void {\n if(connection == undefined){\n this.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n this.connection = undefined;\n return;\n }\n\n if (this.connection) {\n throw new Error(`Connection already set for cluster ${this.clusterID}`);\n }\n\n this.connectionStatus = BridgeClientClusterConnectionStatus.REQUESTING;\n this.connection = connection;\n }\n\n setOldConnection(connection?: BridgeClientConnection): void {\n this.oldConnection = connection;\n }\n\n isUsed(): boolean {\n return this.connection != undefined && this.connectionStatus !== BridgeClientClusterConnectionStatus.DISCONNECTED;\n }\n\n reclustering(connection: BridgeClientConnection): void {\n this.connectionStatus = BridgeClientClusterConnectionStatus.RECLUSTERING;\n this.oldConnection = this.connection;\n this.connection = connection;\n }\n\n addMissedHeartbeat(): void {\n this.missedHeartbeats++;\n }\n\n removeMissedHeartbeat(): void {\n if (this.missedHeartbeats > 0) {\n this.missedHeartbeats--;\n }\n }\n\n resetMissedHeartbeats(): void {\n this.missedHeartbeats = 0;\n }\n}\n\nexport type HeartbeatResponse = {\n cpu: {\n raw: {\n user: number,\n system: number,\n }\n cpuPercent: string\n },\n memory: {\n raw: {\n rss: number,\n heapTotal: number,\n heapUsed: number,\n external: number,\n arrayBuffers: number,\n },\n memoryPercent: string\n usage: number\n },\n ping: number,\n shardPings: {\n id: number,\n ping: number,\n status: number,\n guilds: number,\n members: number\n }[]\n}","import {EventPayload} from \"./EventPayload\";\n\nexport class EventManager {\n\n private pendingPayloads = new Map void;\n reject: (error: unknown) => void;\n }>();\n\n // Track per-request timeout handles so we can clear them on resolve/reject\n private pendingTimeouts = new Map>();\n\n private readonly _send: (payload: EventPayload) => Promise;\n\n private readonly _on: (payload: unknown) => void;\n\n private readonly _request: (payload: unknown) => unknown;\n\n constructor(send: (payload: EventPayload) => Promise, on: (message: unknown) => void, request: (message: unknown) => unknown) {\n this._send = send;\n this._on = on;\n this._request = request\n }\n\n async send(data: unknown) {\n return this._send({\n id: crypto.randomUUID(),\n type: 'message',\n data: data\n });\n }\n\n async request(payload: unknown, timeout: number): Promise {\n const id = crypto.randomUUID();\n\n return new Promise((resolve, reject) => {\n this._send({\n id: id,\n type: 'request',\n data: payload\n });\n\n this.pendingPayloads.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject\n });\n\n const t = setTimeout(() => {\n if (this.pendingPayloads.has(id)) {\n this.pendingPayloads.delete(id);\n this.pendingTimeouts.delete(id);\n reject({\n error: `Request with id ${id} timed out`,\n });\n }\n }, timeout);\n this.pendingTimeouts.set(id, t);\n })\n }\n\n receive(possiblePayload: unknown) {\n if (typeof possiblePayload !== 'object' || possiblePayload === null) {\n return;\n }\n\n const payload = possiblePayload as EventPayload;\n\n if (!payload.id || !payload.type) {\n return;\n }\n\n if (payload.type === 'message') {\n this._on(payload.data);\n return;\n }\n\n if (payload.type === 'response') {\n // Handle requests\n const resolve = this.pendingPayloads.get(payload.id)?.resolve;\n if (resolve) {\n resolve(payload.data);\n this.pendingPayloads.delete(payload.id);\n const to = this.pendingTimeouts.get(payload.id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(payload.id);\n }\n return;\n }\n\n if (payload.type === 'response_error') {\n // Handle requests\n const reject = this.pendingPayloads.get(payload.id)?.reject;\n if (reject) {\n reject(payload.data);\n this.pendingPayloads.delete(payload.id);\n const to = this.pendingTimeouts.get(payload.id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(payload.id);\n }\n return;\n }\n\n if (payload.type === 'request') {\n // Handle requests\n const data = this._request(payload.data);\n if(data instanceof Promise) {\n data.then((result) => {\n this._send({\n id: payload.id,\n type: 'response',\n data: result\n });\n }).catch((error) => {\n this._send({\n id: payload.id,\n type: 'response_error',\n data: error\n });\n });\n } else {\n this._send({\n id: payload.id,\n type: 'response',\n data: data\n });\n }\n return;\n }\n }\n\n // Reject and clear all pending requests to avoid memory leaks when a connection/process closes\n close(reason?: string) {\n if (this.pendingPayloads.size === 0 && this.pendingTimeouts.size === 0) return;\n const err = { error: reason || 'EventManager closed' };\n for (const [id, handlers] of this.pendingPayloads.entries()) {\n try { handlers.reject(err); } catch (_) { /* ignore */ }\n this.pendingPayloads.delete(id);\n const to = this.pendingTimeouts.get(id);\n if (to) clearTimeout(to);\n this.pendingTimeouts.delete(id);\n }\n // In case there are any stray timeouts with no pending payload\n for (const to of this.pendingTimeouts.values()) {\n clearTimeout(to);\n }\n this.pendingTimeouts.clear();\n }\n}\n\n","import {EventManager} from \"../general/EventManager\";\nimport {Connection} from \"net-ipc\";\n\nexport enum BridgeClientConnectionStatus {\n READY = 'ready',\n PENDING_STOP = 'pending_stop',\n}\nexport class BridgeClientConnection {\n public readonly instanceID: number;\n public readonly eventManager: EventManager;\n public readonly connection: Connection;\n public readonly data: unknown;\n public connectionStatus: BridgeClientConnectionStatus = BridgeClientConnectionStatus.READY;\n public readonly dev: boolean = false;\n public readonly establishedAt: number = Date.now();\n\n private _onMessage?: (message: unknown) => void;\n private _onRequest?: (message: unknown) => unknown;\n\n constructor(instanceID: number, connection: Connection, data: unknown, dev: boolean) {\n this.instanceID = instanceID;\n this.connection = connection;\n this.data = data;\n this.dev = dev || false;\n this.eventManager = new EventManager((message) => {\n if(!this.connection?.connection?.closed){\n return this.connection.send(message);\n }\n return Promise.reject(new Error('Connection is closed, cannot send message'));\n }, (message) => {\n if (this._onMessage) {\n this._onMessage(message);\n }\n }, (message) => {\n if (this._onRequest) {\n return this._onRequest(message);\n }\n return undefined;\n })\n }\n\n messageReceive(message: any) {\n this.eventManager.receive(message);\n }\n\n onRequest(callback: (message: unknown) => unknown) {\n this._onRequest = callback;\n }\n\n onMessage(callback: (message: unknown) => void) {\n this._onMessage = callback;\n }\n}","import {Server} from 'net-ipc';\nimport {BridgeClientConnection, BridgeClientConnectionStatus} from \"./BridgeClientConnection\";\nimport {GatewayIntentsString, Snowflake} from \"discord.js\";\nimport {ClusterCalculator} from \"./ClusterCalculator\";\nimport {BridgeClientCluster, BridgeClientClusterConnectionStatus, HeartbeatResponse} from \"./BridgeClientCluster\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport class Bridge {\n public readonly port: number;\n public readonly server: Server;\n public readonly connectedClients: Map = new Map();\n private readonly token: string;\n private readonly intents: GatewayIntentsString[];\n private readonly shardsPerCluster: number = 1;\n private readonly clusterToStart: number = 1\n private readonly reclusteringTimeoutInMs: number;\n\n private readonly clusterCalculator: ClusterCalculator;\n\n private readonly eventMap: BridgeEventListeners = {\n CLUSTER_READY: undefined, CLUSTER_HEARTBEAT_FAILED: undefined,\n CLUSTER_STOPPED: undefined, CLIENT_CONNECTED: undefined, CLIENT_DISCONNECTED: undefined,\n CLUSTER_SPAWNED: undefined, CLUSTER_RECLUSTER: undefined, ERROR: undefined,\n CLIENT_STOP: undefined\n }\n\n constructor(port: number, token: string, intents: GatewayIntentsString[], shardsPerCluster: number, clusterToStart: number, reclusteringTimeoutInMs: number) {\n this.port = port;\n this.token = token;\n this.intents = intents;\n this.clusterToStart = clusterToStart;\n this.shardsPerCluster = shardsPerCluster;\n this.reclusteringTimeoutInMs = reclusteringTimeoutInMs;\n\n this.clusterCalculator = new ClusterCalculator(this.clusterToStart, this.shardsPerCluster);\n\n this.server = new Server({\n port: this.port,\n })\n }\n\n public start(): void {\n this.server.start().then(() => {\n this.startListening();\n })\n\n this.interval();\n }\n\n private interval(): void {\n setInterval(() => {\n this.checkCreate();\n this.checkRecluster();\n this.heartbeat();\n }, 5000)\n }\n\n private checkRecluster(): void {\n // check if all clusters are used\n const up = this.clusterCalculator.checkAllClustersConnected()\n if (!up) {\n return;\n }\n\n const connectedClients: BridgeClientConnection[] = this.connectedClients.values()\n .filter(c => c.connectionStatus == BridgeClientConnectionStatus.READY)\n .filter(c => !c.dev)\n .filter(c => c.establishedAt + this.reclusteringTimeoutInMs < Date.now())\n .toArray();\n\n const {most, least} = this.clusterCalculator.findMostAndLeastClustersForConnections(connectedClients);\n if (most) {\n const clusterToSteal = this.clusterCalculator.getClusterForConnection(most)[0] || undefined;\n if (least && clusterToSteal) {\n clusterToSteal.reclustering(least);\n\n if(this.eventMap.CLUSTER_RECLUSTER) this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection!);\n this.createCluster(least, clusterToSteal, true);\n\n return;\n }\n }\n }\n\n private heartbeat(): void {\n const clusters = this.clusterCalculator.clusterList;\n\n clusters.forEach((cluster) => {\n if(cluster.connection && cluster.connectionStatus == BridgeClientClusterConnectionStatus.CONNECTED && !cluster.heartbeatPending) {\n cluster.heartbeatPending = true;\n cluster.connection.eventManager.request({\n type: 'CLUSTER_HEARTBEAT',\n data: {\n clusterID: cluster.clusterID\n }\n }, 20000).then((r) => {\n cluster.removeMissedHeartbeat();\n cluster.heartbeatResponse = r;\n }).catch((err) => {\n if(this.eventMap.CLUSTER_HEARTBEAT_FAILED) this.eventMap.CLUSTER_HEARTBEAT_FAILED(cluster, err)\n cluster.addMissedHeartbeat()\n\n if(cluster.missedHeartbeats > 7 && !cluster.connection?.dev){\n cluster.connection?.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n cluster.resetMissedHeartbeats()\n }\n }).finally(() => {\n cluster.heartbeatPending = false;\n })\n }\n });\n }\n\n private checkCreate(): void {\n const optionalCluster = this.clusterCalculator.getNextCluster();\n\n if (!optionalCluster) {\n return;\n }\n\n const lowestLoadClient = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients);\n if (!lowestLoadClient) {\n return;\n }\n\n this.createCluster(lowestLoadClient, optionalCluster)\n }\n\n private createCluster(connection: BridgeClientConnection, cluster: BridgeClientCluster, recluster = false) {\n cluster.resetMissedHeartbeats()\n cluster.heartbeatResponse = undefined;\n if (!recluster) {\n cluster.setConnection(connection)\n } else {\n cluster.oldConnection?.eventManager.send({\n type: 'CLUSTER_RECLUSTER',\n data: {\n clusterID: cluster.clusterID\n }\n })\n }\n if(this.eventMap.CLUSTER_SPAWNED) this.eventMap.CLUSTER_SPAWNED(cluster, connection)\n connection.eventManager.send({\n type: 'CLUSTER_CREATE',\n data: {\n clusterID: cluster.clusterID,\n instanceID: connection.instanceID,\n totalShards: this.getTotalShards(),\n shardList: cluster.shardList,\n token: this.token,\n intents: this.intents\n }\n });\n }\n\n public startListening(): void {\n this.server.on('connect', (connection, payload) => {\n const id = payload?.id;\n const data = payload.data as unknown;\n const dev = payload?.dev || false;\n if (!id) {\n connection.close('Invalid payload', false);\n return;\n }\n\n if (this.connectedClients.values().some(client => client.instanceID === id)) {\n connection.close('Already connected', false);\n return;\n }\n\n const bridgeConnection = new BridgeClientConnection(payload.id, connection, data, dev);\n if(this.eventMap.CLIENT_CONNECTED) this.eventMap.CLIENT_CONNECTED(bridgeConnection);\n\n bridgeConnection.onMessage((m: any) => {\n if (m.type == 'CLUSTER_SPAWNED') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.STARTING;\n }\n return;\n }\n\n if (m.type == 'CLUSTER_READY') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.startedAt = Date.now();\n if(this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(cluster, m.data.guilds || 0, m.data.members || 0);\n cluster.connectionStatus = BridgeClientClusterConnectionStatus.CONNECTED;\n if (cluster.oldConnection) {\n cluster.oldConnection.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n cluster.oldConnection = undefined;\n }\n }\n return;\n }\n\n if (m.type == 'CLUSTER_STOPPED') {\n const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find(c => c.clusterID === m.data.id);\n if (cluster) {\n cluster.startedAt = undefined;\n if(this.eventMap.CLUSTER_STOPPED) this.eventMap.CLUSTER_STOPPED(cluster);\n cluster.setConnection(undefined);\n }\n return;\n }\n\n if(m.type == \"INSTANCE_STOP\") {\n this.stopInstance(bridgeConnection);\n }\n\n return;\n })\n\n bridgeConnection.onRequest((m: any) => {\n if(m.type == 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = m.guildID;\n const shardID = ShardingUtil.getShardIDForGuild(guildID, this.getTotalShards());\n const cluster = this.clusterCalculator.getClusterOfShard(shardID);\n if(!cluster){\n return Promise.reject(new Error(\"cluster not found\"))\n }\n if(cluster.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED){\n return Promise.reject(new Error(\"cluster not connected.\"))\n }\n\n if(!cluster.connection?.eventManager){\n return Promise.reject(new Error(\"no connection defined.\"))\n }\n\n return cluster.connection.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n clusterID: cluster.clusterID,\n guildID: guildID,\n data: m.data\n }, 5000)\n }\n\n if(m.type == 'BROADCAST_EVAL') {\n const responses = Promise.all(\n this.connectedClients.values().map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: m.data,\n }, 5000);\n })\n )\n return new Promise((resolve, reject) => {\n responses.then((r) => {\n resolve(r.flatMap(f => f))\n }).catch(reject);\n })\n }\n\n if(m.type == 'SELF_CHECK') {\n return {\n clusterList: [\n ...this.clusterCalculator.getClusterForConnection(bridgeConnection).map(c => c.clusterID),\n ...this.clusterCalculator.getOldClusterForConnection(bridgeConnection).map(c => c.clusterID)\n ]\n }\n }\n\n return Promise.reject(new Error(\"unknown type\"))\n })\n\n this.connectedClients.set(connection.id, bridgeConnection)\n });\n\n this.server.on('disconnect', (connection, reason) => {\n const closedConnection = this.connectedClients.get(connection.id);\n if (!closedConnection) {\n return;\n }\n\n const clusters = this.clusterCalculator.getClusterForConnection(closedConnection);\n for (const cluster of clusters) {\n this.clusterCalculator.clearClusterConnection(cluster.clusterID);\n }\n\n this.connectedClients.delete(connection.id);\n if(this.eventMap.CLIENT_DISCONNECTED) this.eventMap.CLIENT_DISCONNECTED(closedConnection, reason);\n });\n\n this.server.on(\"message\", (message, connection) => {\n this.sendMessageToClient(connection.id, message);\n })\n }\n\n sendMessageToClient(clientId: string, message: unknown): void {\n if (!this.connectedClients.has(clientId)) {\n return;\n }\n\n const client = this.connectedClients.get(clientId);\n if (client) {\n client.messageReceive(message);\n }\n }\n\n private getTotalShards() {\n return this.shardsPerCluster * this.clusterToStart;\n }\n\n\n public on(event: K, listener: BridgeEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public getClusters() {\n return this.clusterCalculator.clusterList;\n }\n\n async stopAllInstances() {\n const instances = Array.from(this.connectedClients.values());\n for (const instance of instances) {\n instance.connectionStatus = BridgeClientConnectionStatus.PENDING_STOP;\n }\n\n for (const instance of instances) {\n await this.stopInstance(instance, false);\n }\n }\n\n async stopAllInstancesWithRestart() {\n const instances = Array.from(this.connectedClients.values());\n\n for (const instance of instances) {\n await this.stopInstance(instance);\n await new Promise((resolve) => {\n setTimeout(async () => {\n resolve();\n }, 1000 * 10);\n })\n }\n }\n\n async moveCluster(instance: BridgeClientConnection, cluster: BridgeClientCluster) {\n cluster.reclustering(instance);\n\n this.createCluster(instance, cluster, true);\n }\n\n async stopInstance(instance: BridgeClientConnection, recluster = true) {\n if(this.eventMap.CLIENT_STOP) this.eventMap.CLIENT_STOP(instance);\n instance.connectionStatus = BridgeClientConnectionStatus.PENDING_STOP;\n\n let clusterToSteal: BridgeClientCluster | undefined;\n\n await instance.eventManager.send({\n type: 'INSTANCE_STOP'\n });\n\n if(recluster) {\n while ((clusterToSteal = this.clusterCalculator.getClusterForConnection(instance).filter(c =>\n c.connectionStatus === BridgeClientClusterConnectionStatus.CONNECTED ||\n c.connectionStatus == BridgeClientClusterConnectionStatus.STARTING ||\n c.connectionStatus == BridgeClientClusterConnectionStatus.RECLUSTERING)[0]) !== undefined) {\n // skip if the cluster is not connected\n if(clusterToSteal.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED) break;\n\n const least = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients);\n if (!least) {\n if (this.eventMap.ERROR) {\n this.eventMap.ERROR(\"Reclustering failed: No least cluster found.\");\n }\n await instance.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: clusterToSteal.clusterID\n }\n });\n clusterToSteal.connection = undefined;\n clusterToSteal.connectionStatus = BridgeClientClusterConnectionStatus.DISCONNECTED;\n continue;\n }\n\n clusterToSteal.reclustering(least);\n\n if (this.eventMap.CLUSTER_RECLUSTER) {\n this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection!);\n }\n\n this.createCluster(least, clusterToSteal, true);\n }\n\n return new Promise((resolve, reject) => {\n const interval = setInterval(async () => {\n const cluster = this.clusterCalculator.getOldClusterForConnection(instance)[0] || undefined;\n if (!cluster) {\n clearInterval(interval);\n await instance.eventManager.send({\n type: 'INSTANCE_STOPPED'\n })\n await instance.connection.close(\"Instance stopped.\", false);\n resolve();\n return;\n }\n }, 1000);\n })\n } else {\n for(const cluster of this.clusterCalculator.getClusterForConnection(instance)) {\n await instance.eventManager.send({\n type: 'CLUSTER_STOP',\n data: {\n id: cluster.clusterID\n }\n });\n }\n\n\n await instance.eventManager.send({\n type: 'INSTANCE_STOPPED'\n })\n await instance.connection.close(\"Instance stopped.\", false);\n }\n }\n\n sendRequestToGuild(cluster: BridgeClientCluster, guildID: Snowflake, data: unknown, timeout = 5000): Promise {\n if(!cluster.connection){\n return Promise.reject(new Error(\"No connection defined for cluster \" + cluster.clusterID));\n }\n\n return cluster.connection.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n clusterID: cluster.clusterID,\n guildID: guildID,\n data: data\n }, timeout);\n }\n}\n\n\n\nexport type BridgeEventListeners = {\n 'CLUSTER_READY': ((cluster: BridgeClientCluster, guilds: number, members: number) => void) | undefined,\n 'CLUSTER_STOPPED': ((cluster: BridgeClientCluster) => void) | undefined,\n 'CLUSTER_SPAWNED': ((cluster: BridgeClientCluster, connection: BridgeClientConnection) => void) | undefined,\n 'CLUSTER_RECLUSTER': ((cluster: BridgeClientCluster, newConnection: BridgeClientConnection, oldConnection: BridgeClientConnection) => void) | undefined,\n 'CLUSTER_HEARTBEAT_FAILED': ((cluster: BridgeClientCluster, error: unknown) => void) | undefined,\n 'CLIENT_CONNECTED': ((client: BridgeClientConnection) => void) | undefined,\n 'CLIENT_DISCONNECTED': ((client: BridgeClientConnection, reason: string) => void) | undefined,\n 'ERROR': ((error: string) => void) | undefined,\n 'CLIENT_STOP': ((instance: BridgeClientConnection) => void) | undefined\n};","import {BridgeClientCluster, BridgeClientClusterConnectionStatus} from \"./BridgeClientCluster\";\nimport {BridgeClientConnection, BridgeClientConnectionStatus} from \"./BridgeClientConnection\";\n\n/**\n * Manages the calculation and distribution of clusters for a Discord bot sharding system.\n * This class is responsible for creating clusters with their assigned shards,\n * tracking which clusters are in use, and providing methods to retrieve available clusters.\n */\nexport class ClusterCalculator {\n /** The total number of clusters to initialize */\n private readonly clusterToStart: number;\n\n /** The number of shards that each cluster will manage */\n private readonly shardsPerCluster: number;\n\n /** List of all clusters managed by this calculator */\n public readonly clusterList: BridgeClientCluster[]= [];\n\n /**\n * Creates a new ClusterCalculator and initializes the clusters.\n * \n * @param clusterToStart - The number of clusters to create\n * @param shardsPerCluster - The number of shards each cluster will manage\n */\n constructor(clusterToStart: number, shardsPerCluster: number) {\n this.shardsPerCluster = shardsPerCluster;\n this.clusterToStart = clusterToStart;\n\n this.calculateClusters();\n }\n\n /**\n * Calculates and initializes all clusters with their assigned shards.\n * Each cluster is assigned a sequential range of shard IDs based on its cluster index.\n */\n private calculateClusters(): void {\n const clusters: Map = new Map();\n for (let i = 0; i < this.clusterToStart; i++) {\n clusters.set(i, []);\n for (let j = 0; j < this.shardsPerCluster; j++) {\n clusters.get(i)?.push(i * this.shardsPerCluster + j);\n }\n }\n\n for (let [clusterIndex, clusterShards] of clusters.entries()) {\n this.clusterList.push(new BridgeClientCluster(clusterIndex, clusterShards));\n }\n }\n\n /**\n * Retrieves the next available (unused) cluster and marks it as used.\n * \n * @returns The next available cluster, or undefined if all clusters are in use\n */\n public getNextCluster(): BridgeClientCluster | undefined {\n for (const cluster of this.clusterList) {\n if (!cluster.isUsed()) {\n return cluster;\n }\n }\n return undefined; // No available clusters\n }\n\n /**\n * Retrieves multiple available clusters up to the specified count.\n * Each returned cluster is marked as used.\n * \n * @param count - The maximum number of clusters to retrieve\n * @returns An array of available clusters (may be fewer than requested if not enough are available)\n */\n public getNextClusters(count: number): BridgeClientCluster[] {\n const availableClusters: BridgeClientCluster[] = [];\n for (const cluster of this.clusterList) {\n if (!cluster.isUsed() && availableClusters.length < count) {\n availableClusters.push(cluster);\n }\n }\n return availableClusters; // Returns the clusters that were found\n }\n\n /**\n * Sets the used status of a specific cluster by its ID.\n *\n * @param clusterID - The ID of the cluster to update\n * @param connection - The connection to associate with the cluster\n */\n public clearClusterConnection(clusterID: number): void {\n const cluster = this.clusterList.find(c => c.clusterID === clusterID);\n if (cluster) {\n cluster.setConnection(undefined);\n }\n }\n\n public getClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[] {\n return this.clusterList.filter(cluster =>\n cluster.connection?.instanceID === connection.instanceID\n );\n }\n\n public getOldClusterForConnection(connection: BridgeClientConnection): BridgeClientCluster[] {\n return this.clusterList.filter(cluster =>\n cluster.oldConnection?.instanceID === connection.instanceID\n );\n }\n\n public checkAllClustersConnected(): boolean {\n for (const cluster of this.clusterList) {\n if (cluster.connectionStatus != BridgeClientClusterConnectionStatus.CONNECTED){\n return false; // At least one cluster is not in use\n }\n }\n return true; // All clusters are in use\n }\n\n\n findMostAndLeastClustersForConnections(\n connectedClients: BridgeClientConnection[]\n ): {\n most: BridgeClientConnection | undefined,\n least: BridgeClientConnection | undefined\n } {\n\n const openClients = connectedClients.filter(x => !x.dev)\n\n const devClients = connectedClients.filter(x => x.dev)\n const summDevConnectedClusters = devClients.map(c => this.getClusterForConnection(c).length).reduce((a, b) => a + b, 0);\n\n let most: BridgeClientConnection | undefined;\n let least: BridgeClientConnection | undefined;\n let remainder = ((this.clusterToStart - summDevConnectedClusters) % openClients.length || 0);\n\n for (const client of openClients) {\n const clusters = this.getClusterForConnection(client);\n\n if (!most || clusters.length > this.getClusterForConnection(most).length) {\n most = client;\n }\n\n if (!least || clusters.length < this.getClusterForConnection(least).length) {\n least = client;\n }\n }\n\n if (most && least) {\n const mostCount = this.getClusterForConnection(most).length;\n const leastCount = this.getClusterForConnection(least).length;\n\n // Only recluster if the difference is greater than remainder\n if (mostCount - leastCount <= remainder) {\n return {most: undefined, least: undefined};\n }\n }\n\n return {most, least};\n }\n\n getClusterWithLowestLoad(connectedClients: Map): BridgeClientConnection | undefined {\n let lowestLoadClient: BridgeClientConnection | undefined;\n let lowestLoad = Infinity;\n\n for (const client of connectedClients.values().filter(c =>\n c.connectionStatus === BridgeClientConnectionStatus.READY && !c.dev)) {\n const clusters = this.getClusterForConnection(client);\n\n const load = clusters.length; // Assuming load is determined by the number of clusters assigned\n if (load < lowestLoad) {\n lowestLoad = load;\n lowestLoadClient = client;\n }\n }\n\n return lowestLoadClient; // Returns the client with the lowest load, or undefined if no clients are connected\n }\n\n getClusterOfShard(shardID: number) {\n return this.clusterList.find(c => c.shardList.includes(shardID));\n }\n}\n","export class ShardingUtil {\n public static getShardIDForGuild(guildID: string, totalShards: number): number {\n if (!guildID || totalShards <= 0) {\n throw new Error(\"Invalid guild ID or total shards\");\n }\n\n return Number(BigInt(guildID) >> 22n) % totalShards;\n }\n}","import {Client, GatewayIntentsString, Status} from \"discord.js\";\nimport {EventManager} from \"../general/EventManager\";\nimport os from \"os\";\nexport class Cluster {\n\n public readonly instanceID: number;\n\n public readonly clusterID: number;\n\n public readonly shardList: number[] = [];\n\n public readonly totalShards: number;\n\n public readonly token: string;\n\n public readonly intents: GatewayIntentsString[];\n\n public eventManager: EventManager;\n\n public client!: T;\n\n public onSelfDestruct?: () => void;\n\n private readonly eventMap: {\n 'message': ((message: unknown) => void) | undefined,\n 'request': ((message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined,\n 'CLUSTER_READY': (() => void) | undefined,\n } = {\n message: undefined, request: undefined, CLUSTER_READY: undefined,\n }\n\n constructor(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]) {\n this.instanceID = instanceID;\n this.clusterID = clusterID;\n this.shardList = shardList;\n this.totalShards = totalShards;\n this.token = token;\n this.intents = intents;\n this.eventManager = new EventManager((message: unknown) => {\n return new Promise((resolve, reject) => {\n if (typeof process.send !== 'function') {\n reject(new Error(\"Process does not support sending messages\"));\n return;\n }\n\n process.send?.(message, undefined, undefined, (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }, (message: unknown) => {\n this._onMessage(message);\n }, (message: unknown) => {\n return this._onRequest(message);\n });\n process.on(\"message\", (message) => {\n this.eventManager.receive(message);\n })\n }\n\n static initial(): Cluster {\n const args = process.env;\n\n if (args.SHARD_LIST == undefined || args.INSTANCE_ID == undefined || args.TOTAL_SHARDS == undefined || args.TOKEN == undefined || args.INTENTS == undefined || args.CLUSTER_ID == undefined) {\n throw new Error(\"Missing required environment variables\");\n }\n\n const shardList = args.SHARD_LIST.split(',').map(Number);\n\n const totalShards = Number(args.TOTAL_SHARDS);\n\n const instanceID = Number(args.INSTANCE_ID);\n const clusterID = Number(args.CLUSTER_ID);\n\n const token = args.TOKEN;\n\n const intents = args.INTENTS.split(',').map(i => i.trim()) as GatewayIntentsString[];\n\n return new Cluster(instanceID, clusterID, shardList, totalShards, token, intents);\n }\n\n triggerReady(guilds: number, members: number) {\n this.eventManager.send({\n type: 'CLUSTER_READY',\n id: this.clusterID,\n guilds: guilds,\n members: members,\n });\n\n if(this.eventMap?.CLUSTER_READY) {\n this.eventMap?.CLUSTER_READY();\n }\n }\n\n triggerError(e: any) {\n this.eventManager.send({\n type: 'CLUSTER_ERROR',\n id: this.clusterID,\n });\n }\n\n private async wait(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n private _onMessage(message: unknown): void {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CUSTOM' && this.eventMap.message) {\n this.eventMap.message!(m.data);\n }\n }\n\n private _onRequest(message: unknown): unknown {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(m.data, resolve, reject);\n });\n } else if(m.type == 'CLUSTER_HEARTBEAT'){\n const startTime = process.hrtime.bigint();\n const startUsage = process.cpuUsage();\n\n (async () => {\n await this.wait(500);\n })();\n\n const endTime = process.hrtime.bigint();\n const usageDiff = process.cpuUsage(startUsage);\n\n const elapsedTimeUs = Number((endTime - startTime) / 1000n);\n const totalCPUTime = usageDiff.user + usageDiff.system;\n\n const cpuCount = os.cpus().length;\n const cpuPercent = (totalCPUTime / (elapsedTimeUs * cpuCount)) * 100;\n\n // Collect per-shard ping information in addition to the overall ws ping\n let shardPings: { id: number, ping: number, status: Status, uptime?: unknown, guilds: number, members: number }[] = [];\n try {\n const shards = this.client.ws.shards;\n\n if(shards) {\n shards.forEach((shard) => {\n shardPings.push({ id: shard.id, ping: shard.ping, status: shard.status,\n guilds: this.client.guilds.cache.filter(g => g.shardId === shard.id).size,\n members: this.client.guilds.cache.filter(g => g.shardId === shard.id).reduce((acc, g) => acc + g.memberCount, 0)\n });\n\n this.client.shard?.fetchClientValues('uptime', shard.id).then(values => {\n shardPings[shard.id][\"uptime\"] = values\n console.log(values)\n }).catch(e => {\n\n })\n })\n }\n } catch (_) {\n // ignore and keep empty shardPings on failure\n }\n\n return {\n cpu: { raw: process.cpuUsage(), cpuPercent: cpuPercent.toFixed(2) },\n memory: { raw: process.memoryUsage(),\n memoryPercent: ((process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100).toFixed(2) + '%',\n usage: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + 'MB'\n },\n ping: this.client.ws.ping,\n shardPings: shardPings,\n }\n } else if(m.type == 'BROADCAST_EVAL'){\n const broadcast = message as { type: 'BROADCAST_EVAL', data: string }\n\n const fn = eval(`(${broadcast.data})`);\n\n const result = fn(this.client);\n if(result instanceof Promise){\n return new Promise((resolve, reject) => {\n result.then(res => {\n resolve(res);\n }).catch(err => {\n reject(err);\n });\n });\n } else {\n return result;\n }\n } else if(m.type == 'SELF_DESTRUCT') {\n if(this.onSelfDestruct) {\n this.onSelfDestruct();\n }\n }\n return undefined;\n }\n\n public on(event: K, listener: ClusterEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public sendMessage(data: unknown) {\n this.eventManager.send({\n type: 'CUSTOM',\n data: data,\n });\n }\n\n public sendRequest(data: unknown, timeout = 5000): Promise {\n return this.eventManager.request({\n type: 'CUSTOM',\n data: data,\n }, timeout);\n }\n\n public broadcastEval(fn: (cluster: T) => Result, timeout = 20000): Promise {\n return this.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: fn.toString(),\n }, timeout);\n }\n\n\n public sendMessageToClusterOfGuild(guildID: string, message: unknown): void {\n if (this.eventManager) {\n this.eventManager.send({\n type: 'REDIRECT_MESSAGE_TO_GUILD',\n guildID: guildID,\n data: message\n });\n }\n }\n\n public sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n if (this.eventManager) {\n this.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n guildID: guildID,\n data: message\n }, timeout).then((response) => {\n resolve(response);\n }).catch((error) => {\n reject(error);\n });\n } else {\n reject(new Error(\"Event manager is not initialized\"));\n }\n });\n }\n}\n\nexport type ClusterEventListeners = {\n message: (message: unknown) => void;\n request: (message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void;\n\n CLUSTER_READY: () => void;\n};","import {ChildProcess} from \"child_process\";\nimport {EventManager} from \"../general/EventManager\";\n\nexport type ClusterProcessState = 'starting' | 'running' | 'stopped';\n\nexport class ClusterProcess {\n public readonly child: ChildProcess;\n public readonly eventManager: EventManager;\n public readonly id: number;\n public readonly shardList: number[];\n public readonly totalShards: number;\n public status: ClusterProcessState;\n public readonly createdAt: number = Date.now();\n\n private _onMessage?: (message: unknown) => void;\n private _onRequest?: (message: unknown) => unknown;\n\n constructor(id: number, child: ChildProcess, shardList: number[], totalShards: number) {\n this.id = id;\n this.child = child;\n this.shardList = shardList;\n this.totalShards = totalShards;\n this.status = 'starting';\n this.eventManager = new EventManager((message) => {\n return new Promise((resolve, reject) => {\n this.child.send(message, (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n })\n }, (message) => {\n if (this._onMessage) {\n this._onMessage(message);\n }\n }, (message) => {\n if (this._onRequest) {\n return this._onRequest(message);\n }\n return undefined;\n })\n\n this.child.on('message', (message) => {\n this.eventManager.receive(message);\n });\n\n // Ensure we do not retain pending requests if the child dies or errors\n this.child.on('exit', () => {\n this.eventManager.close('child process exited');\n });\n this.child.on('error', () => {\n this.eventManager.close('child process error');\n });\n }\n\n onMessage(callback: (message: unknown) => void) {\n this._onMessage = callback;\n }\n\n onRequest(callback: (message: unknown) => unknown) {\n this._onRequest = callback;\n }\n\n public sendMessage(data: unknown) {\n this.eventManager.send({\n type: 'CUSTOM',\n data: data,\n });\n }\n\n public sendRequest(data: unknown, timeout = 5000): Promise {\n return this.eventManager.request({\n type: 'CUSTOM',\n data: data,\n }, timeout);\n }\n}","import {fork} from 'child_process';\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport abstract class BotInstance {\n\n private readonly entryPoint: string;\n\n private readonly execArgv: string[];\n\n public readonly clients: Map = new Map();\n\n protected constructor(entryPoint: string, execArgv?: string[]) {\n this.entryPoint = entryPoint;\n this.execArgv = execArgv ?? [];\n }\n\n protected readonly eventMap: BotInstanceEventListeners = {\n 'message': undefined,\n 'request': undefined,\n\n 'PROCESS_KILLED': undefined,\n 'PROCESS_SELF_DESTRUCT_ERROR': undefined,\n 'PROCESS_SPAWNED': undefined,\n 'ERROR': undefined,\n 'PROCESS_ERROR': undefined,\n 'CLUSTER_READY': undefined,\n 'CLUSTER_ERROR': undefined,\n 'CLUSTER_RECLUSTER': undefined,\n 'BRIDGE_CONNECTION_ESTABLISHED': undefined,\n 'BRIDGE_CONNECTION_CLOSED': undefined,\n 'BRIDGE_CONNECTION_STATUS_CHANGE': undefined,\n 'INSTANCE_STOP': undefined,\n 'INSTANCE_STOPPED': undefined,\n 'SELF_CHECK_SUCCESS': undefined,\n 'SELF_CHECK_ERROR': undefined,\n 'SELF_CHECK_RECEIVED': undefined,\n }\n\n protected startProcess(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]): void {\n try {\n const child = fork(this.entryPoint, {\n env: {\n INSTANCE_ID: instanceID.toString(),\n CLUSTER_ID: clusterID.toString(),\n SHARD_LIST: shardList.join(','),\n TOTAL_SHARDS: totalShards.toString(),\n TOKEN: token,\n INTENTS: intents.join(','),\n FORCE_COLOR: 'true'\n },\n stdio: 'inherit',\n execArgv: this.execArgv,\n silent: false,\n detached: true,\n })\n\n const client = new ClusterProcess(clusterID, child, shardList, totalShards);\n\n child.stdout?.on('data', (data) => {\n process.stdout.write(data);\n });\n\n child.stderr?.on('data', (data) => {\n process.stderr.write(data);\n });\n\n child.on(\"spawn\", () => {\n if(this.eventMap.PROCESS_SPAWNED) this.eventMap.PROCESS_SPAWNED(client);\n\n this.setClusterSpawned(client);\n\n this.clients.set(clusterID, client);\n\n client.onMessage((message) => {\n this.onMessage(client, message);\n })\n\n client.onRequest((message) => {\n return this.onRequest(client, message);\n });\n });\n\n child.on(\"error\", (err) => {\n if(this.eventMap.PROCESS_ERROR) this.eventMap.PROCESS_ERROR(client, err);\n })\n\n child.on(\"exit\", (err: Error) => {\n if(client.status !== 'stopped') {\n client.status = 'stopped';\n this.killProcess(client, `Process exited: ${err?.message}`);\n }\n })\n } catch (error) {\n throw new Error(`Failed to start process for cluster ${clusterID}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n protected killProcess(client: ClusterProcess, reason: string): void {\n client.status = 'stopped';\n\n client.eventManager.request({\n type: 'SELF_DESTRUCT',\n reason: reason\n }, 5000).catch(() => {\n if(this.eventMap.PROCESS_SELF_DESTRUCT_ERROR) this.eventMap.PROCESS_SELF_DESTRUCT_ERROR(client, reason, 'Cluster didnt respond to shot-call.');\n }).finally(() => {\n if (client.child && client.child.pid) {\n if(client.child.kill(\"SIGKILL\")) {\n if(this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, true);\n } else {\n if(this.eventMap.ERROR) this.eventMap.ERROR(`Failed to kill process for cluster ${client.id}`);\n client.child.kill(\"SIGKILL\");\n }\n try { process.kill(-client.child.pid) } catch {}\n } else {\n if(this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, false);\n }\n this.clients.delete(client.id);\n this.setClusterStopped(client, reason);\n })\n }\n\n protected abstract setClusterStopped(client: ClusterProcess, reason: string): void;\n\n protected abstract setClusterReady(client: ClusterProcess, guilds: number, members: number): void;\n\n protected abstract setClusterSpawned(client: ClusterProcess): void;\n\n public abstract start(): void;\n\n private onMessage(client: ClusterProcess, message: any): void {\n if(message.type === 'CLUSTER_READY') {\n client.status = 'running';\n if(this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(client);\n this.setClusterReady(client, message.guilds || 0, message.members || 0);\n }\n\n if (message.type === 'CLUSTER_ERROR') {\n client.status = 'stopped';\n if(this.eventMap.CLUSTER_ERROR) this.eventMap.CLUSTER_ERROR(client, message.error);\n this.killProcess(client, 'Cluster error: ' + message.error);\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.message) {\n this.eventMap.message!(client, message.data);\n }\n }\n\n protected abstract onRequest(client: ClusterProcess, message: any): Promise;\n\n public on(event: K, listener: BotInstanceEventListeners[K]): void {\n this.eventMap[event] = listener;\n }\n\n public sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n for (const client of this.clients.values()) {\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if (client.shardList.includes(shardID)) {\n client.eventManager.request({\n type: 'CUSTOM',\n data: message\n }, timeout).then(resolve).catch(reject);\n return;\n }\n }\n reject(new Error(`No cluster found for guild ${guildID}`));\n });\n }\n\n public sendRequestToCluster(cluster: ClusterProcess, message: unknown, timeout = 5000): Promise {\n return new Promise((resolve, reject) => {\n cluster.eventManager.request({\n type: 'CUSTOM',\n data: message\n }, timeout).then(resolve).catch(reject);\n return;\n });\n }\n}\n\nexport type BotInstanceEventListeners = {\n 'message': ((client: ClusterProcess,message: unknown) => void) | undefined,\n 'request': ((client: ClusterProcess, message: unknown, resolve: (data: unknown) => void, reject: (error: any) => void) => void) | undefined,\n\n 'PROCESS_KILLED': ((client: ClusterProcess, reason: string, processKilled: boolean) => void) | undefined,\n 'PROCESS_SELF_DESTRUCT_ERROR': ((client: ClusterProcess, reason: string, error: unknown) => void) | undefined,\n 'PROCESS_SPAWNED': ((client: ClusterProcess) => void) | undefined,\n 'PROCESS_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined,\n 'CLUSTER_READY': ((client: ClusterProcess) => void) | undefined,\n 'CLUSTER_ERROR': ((client: ClusterProcess, error: unknown) => void) | undefined,\n 'CLUSTER_RECLUSTER': ((client: ClusterProcess) => void) | undefined,\n 'ERROR': ((error: string) => void) | undefined,\n\n 'BRIDGE_CONNECTION_ESTABLISHED': (() => void) | undefined,\n 'BRIDGE_CONNECTION_CLOSED': ((reason: string) => void) | undefined,\n 'BRIDGE_CONNECTION_STATUS_CHANGE': ((status: number) => void) | undefined,\n 'INSTANCE_STOP': (() => void) | undefined,\n 'INSTANCE_STOPPED': (() => void) | undefined,\n\n 'SELF_CHECK_SUCCESS': (() => void) | undefined,\n 'SELF_CHECK_ERROR': ((error: string) => void) | undefined,\n 'SELF_CHECK_RECEIVED': ((data: { clusterList: number[] }) => void) | undefined,\n};","import {BotInstance} from \"./BotInstance\";\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {Client} from \"net-ipc\";\nimport {EventManager} from \"../general/EventManager\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport enum BridgeConnectionStatus {\n CONNECTED,\n DISCONNECTED,\n}\n\nexport class ManagedInstance extends BotInstance {\n\n private readonly host: string;\n\n private readonly port: number;\n\n private readonly instanceID: number;\n\n private eventManager!: EventManager;\n\n private connectionStatus: BridgeConnectionStatus = BridgeConnectionStatus.DISCONNECTED;\n\n private data: unknown;\n\n private dev: boolean = false;\n\n constructor(entryPoint: string, host: string, port: number, instanceID: number, data: unknown, execArgv?: string[], dev?: boolean) {\n super(entryPoint, execArgv);\n\n this.host = host;\n this.port = port;\n this.instanceID = instanceID;\n this.data = data;\n this.dev = dev || false;\n }\n\n public start() {\n const client = new Client({\n host: this.host,\n port: this.port,\n reconnect: true,\n retries: 100\n });\n\n this.eventManager = new EventManager((message) => {\n if(client.status == 3) {\n return client.send(message);\n }\n return Promise.reject(new Error('Client is not ready to send messages'));\n }, (message) => {\n const m = message as { type: string, data: unknown };\n if(m.type == 'CLUSTER_CREATE') {\n this.onClusterCreate(m.data)\n } else if (m.type == 'CLUSTER_STOP') {\n this.onClusterStop(m.data);\n } else if(m.type == 'CLUSTER_RECLUSTER') {\n this.onClusterRecluster(m.data);\n } else if(m.type == 'INSTANCE_STOP') {\n if(this.eventMap.INSTANCE_STOP) {\n this.eventMap.INSTANCE_STOP();\n }\n } else if(m.type == 'INSTANCE_STOPPED') {\n if(this.eventMap.INSTANCE_STOPPED) {\n this.eventMap.INSTANCE_STOPPED();\n }\n }\n }, (message) => {\n return this.onBridgeRequest(message);\n });\n\n setInterval(() => {\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.selfCheck();\n }\n }, 2500); // 5 minutes\n\n client.connect({\n id: this.instanceID,\n dev: this.dev,\n data: this.data,\n }).then(_ => {\n if(this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED();\n this.connectionStatus = BridgeConnectionStatus.CONNECTED;\n\n client.on(\"message\", (message) => {\n this.eventManager?.receive(message);\n })\n client.on(\"close\", (reason) => {\n if(this.eventMap.BRIDGE_CONNECTION_CLOSED) this.eventMap.BRIDGE_CONNECTION_CLOSED(reason);\n\n // kill all\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.clients.forEach((client) => {\n this.killProcess(client, 'Bridge connection closed');\n });\n }\n this.connectionStatus = BridgeConnectionStatus.DISCONNECTED;\n });\n\n client.on(\"status\", (status) => {\n if(this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE) this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE(status);\n\n if(status == 4){\n if(this.connectionStatus == BridgeConnectionStatus.CONNECTED) {\n this.clients.forEach((client) => {\n this.killProcess(client, 'Bridge connection closed');\n });\n }\n this.connectionStatus = BridgeConnectionStatus.DISCONNECTED;\n } else if(status == 3){\n this.connectionStatus = BridgeConnectionStatus.CONNECTED;\n if(this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED();\n }\n });\n })\n }\n\n private selfCheck() {\n this.eventManager.request({\n type: 'SELF_CHECK'\n }, 1000 * 60).then((r) => {\n const response = r as { clusterList: number[] };\n\n if(this.eventMap.SELF_CHECK_RECEIVED) {\n this.eventMap.SELF_CHECK_RECEIVED(response);\n }\n\n const startingClusters = this.clients.values().filter(c => c.status == 'starting').toArray();\n startingClusters.forEach((c: ClusterProcess) => {\n if (Date.now() - c.createdAt > 10 * 60 * 1000) {\n this.killProcess(c, 'Cluster took too long to start');\n }\n })\n\n // check if there is an wrong cluster on this host\n const wrongClusters = this.clients.values().filter(c => !response.clusterList.includes(c.id)).toArray();\n if(wrongClusters.length > 0) {\n if(this.eventMap.SELF_CHECK_ERROR) {\n this.eventMap.SELF_CHECK_ERROR(`Self check found wrong clusters: ${wrongClusters.map(c => c.id).join(', ')}`);\n }\n wrongClusters.forEach(c => {\n this.killProcess(c, 'Self check found wrong cluster');\n });\n } else {\n if(this.eventMap.SELF_CHECK_SUCCESS) {\n this.eventMap.SELF_CHECK_SUCCESS();\n }\n }\n }).catch((err) => {\n if(this.eventMap.SELF_CHECK_ERROR) {\n this.eventMap.SELF_CHECK_ERROR(`Self check failed: ${err}`);\n }\n });\n }\n\n protected setClusterStopped(client: ClusterProcess, reason: string): void {\n this.eventManager?.send({\n type: 'CLUSTER_STOPPED',\n data: {\n id: client.id,\n reason: reason\n }\n }).catch(() => {\n return null\n });\n }\n\n protected setClusterReady(client: ClusterProcess, guilds: number, members: number): void {\n this.eventManager?.send({\n type: 'CLUSTER_READY',\n data: {\n id: client.id,\n guilds: guilds,\n members: members\n }\n });\n }\n\n protected setClusterSpawned(client: ClusterProcess): void {\n this.eventManager?.send({\n type: 'CLUSTER_SPAWNED',\n data: {\n id: client.id\n }\n });\n }\n\n private onClusterCreate(message: unknown){\n const m = message as { clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[] }\n\n if(this.clients.has(m.clusterID)) {\n this.eventManager?.send({\n type: 'CLUSTER_STOPPED',\n data: {\n id: m.clusterID,\n reason: 'Cluster already exists'\n }\n }).catch(() => {\n return null\n });\n return;\n }\n\n this.startProcess(this.instanceID, m.clusterID, m.shardList, m.totalShards, m.token, m.intents);\n }\n\n private onClusterStop(message: unknown) {\n const m = message as { id: number };\n const cluster = this.clients.get(m.id)\n if (cluster) {\n this.killProcess(cluster, `Request to stop cluster ${m.id}`);\n }\n }\n\n private onClusterRecluster(message: unknown) {\n const m = message as { clusterID: number }\n const cluster = this.clients.get(m.clusterID);\n if (this.eventMap.CLUSTER_RECLUSTER && cluster) {\n this.eventMap.CLUSTER_RECLUSTER(cluster);\n }\n }\n\n protected onRequest(client: ClusterProcess, message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = message.guildID;\n const data = message.data;\n\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if(client.shardList.includes(shardID)) {\n return client.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return this.eventManager.request({\n type: 'REDIRECT_REQUEST_TO_GUILD',\n guildID: guildID,\n data: data\n }, 5000)\n }\n }\n\n if(message.type == 'BROADCAST_EVAL') {\n return this.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data\n }, 5000)\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(client, message.data, resolve, reject);\n });\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n private onBridgeRequest(message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const clusterID = message.clusterID;\n const data = message.data;\n\n const cluster = this.clients.get(clusterID);\n if(cluster){\n return cluster.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`));\n }\n } else if(message.type == 'CLUSTER_HEARTBEAT'){\n const clusterID = message.data.clusterID;\n const cluster = this.clients.get(clusterID);\n if (cluster) {\n return new Promise((resolve, reject) => {\n cluster.eventManager.request({\n type: 'CLUSTER_HEARTBEAT'\n }, 15000).then((r) => {\n resolve(r);\n }).catch((err) => {\n reject(err);\n });\n })\n } else {\n return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`));\n }\n } else if(message.type == 'BROADCAST_EVAL') {\n return Promise.all(this.clients.values().filter(c => c.status == 'running').map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data,\n }, 5000);\n }));\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n stopInstance(): void {\n this.eventManager?.send({\n type: 'INSTANCE_STOP'\n });\n }\n\n}","import {BotInstance} from \"./BotInstance\";\nimport {ClusterProcess} from \"../cluster/ClusterProcess\";\nimport {GatewayIntentsString} from \"discord.js\";\nimport {ShardingUtil} from \"../general/ShardingUtil\";\n\nexport class StandaloneInstance extends BotInstance {\n private readonly totalClusters: number;\n private readonly shardsPerCluster: number;\n\n public readonly token: string;\n public readonly intents: GatewayIntentsString[];\n\n constructor(entryPoint: string, shardsPerCluster: number, totalClusters: number, token: string, intents: GatewayIntentsString[], execArgv?: string[]) {\n super(entryPoint, execArgv);\n this.shardsPerCluster = shardsPerCluster;\n this.totalClusters = totalClusters;\n this.token = token;\n this.intents = intents;\n }\n\n get totalShards(): number {\n return this.shardsPerCluster * this.totalClusters;\n }\n\n private calculateClusters(): Record {\n const clusters: Record = {};\n for (let i = 0; i < this.totalClusters; i++) {\n clusters[i] = [];\n for (let j = 0; j < this.shardsPerCluster; j++) {\n clusters[i].push(i * this.shardsPerCluster + j);\n }\n }\n return clusters;\n }\n\n public start(): void {\n const clusters = this.calculateClusters();\n for (const [id, shardList] of Object.entries(clusters)) {\n this.startProcess(1, Number(id), shardList, this.totalShards, this.token, this.intents);\n }\n }\n\n protected setClusterStopped(client: ClusterProcess, reason: string): void {\n this.clients.delete(client.id);\n this.restartProcess(client);\n }\n\n protected setClusterReady(client: ClusterProcess): void {\n \n }\n\n protected setClusterSpawned(client: ClusterProcess): void {\n\n }\n\n private restartProcess(client: ClusterProcess): void {\n this.startProcess(1, client.id, client.shardList, this.totalShards, this.token, this.intents);\n }\n\n protected onRequest(client: ClusterProcess, message: any): Promise {\n if(message.type === 'REDIRECT_REQUEST_TO_GUILD'){\n const guildID = message.guildID;\n const data = message.data;\n\n const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);\n if(client.shardList.includes(shardID)) {\n return client.eventManager.request({\n type: 'CUSTOM',\n data: data\n }, 5000)\n } else {\n return Promise.reject(new Error(`Shard ID ${shardID} not found in cluster ${client.id} for guild ${guildID}`));\n }\n }\n\n if(message.type == 'BROADCAST_EVAL') {\n return Promise.all(\n this.clients.values().map(c => {\n return c.eventManager.request({\n type: 'BROADCAST_EVAL',\n data: message.data,\n }, 5000);\n })\n );\n }\n\n if(message.type == 'CUSTOM' && this.eventMap.request) {\n return new Promise((resolve, reject) => {\n this.eventMap.request!(client, message.data, resolve, reject);\n });\n }\n\n return Promise.reject(new Error(`Unknown request type: ${message.type}`));\n }\n\n}"],"mappings":"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","names":["BridgeClientClusterConnectionStatus","result","BridgeClientConnectionStatus","message","m","message","message","m","fn","message","message","BridgeConnectionStatus","message","m","client","message"]} \ No newline at end of file diff --git a/src/bridge/Bridge.ts b/src/bridge/Bridge.ts index 83de476..d17bf3b 100644 --- a/src/bridge/Bridge.ts +++ b/src/bridge/Bridge.ts @@ -19,9 +19,9 @@ export class Bridge { private readonly eventMap: BridgeEventListeners = { CLUSTER_READY: undefined, CLUSTER_HEARTBEAT_FAILED: undefined, - CLUSTER_STOPPED: undefined, CLIENT_CONNECTED: undefined, CLIENT_DISCONNECTED: undefined, + CLUSTER_STOPPED: undefined, HOST_CONNECTED: undefined, HOST_DISCONNECTED: undefined, CLUSTER_SPAWNED: undefined, CLUSTER_RECLUSTER: undefined, ERROR: undefined, - CLIENT_STOP: undefined + HOST_STOP: undefined } constructor(port: number, token: string, intents: GatewayIntentsString[], shardsPerCluster: number, clusterToStart: number, reclusteringTimeoutInMs: number) { @@ -175,7 +175,7 @@ export class Bridge { } const bridgeConnection = new BridgeClientConnection(payload.id, connection, data, dev); - if(this.eventMap.CLIENT_CONNECTED) this.eventMap.CLIENT_CONNECTED(bridgeConnection); + if(this.eventMap.HOST_CONNECTED) this.eventMap.HOST_CONNECTED(bridgeConnection); bridgeConnection.onMessage((m: any) => { if (m.type == 'CLUSTER_SPAWNED') { @@ -289,7 +289,7 @@ export class Bridge { } this.connectedClients.delete(connection.id); - if(this.eventMap.CLIENT_DISCONNECTED) this.eventMap.CLIENT_DISCONNECTED(closedConnection, reason); + if(this.eventMap.HOST_DISCONNECTED) this.eventMap.HOST_DISCONNECTED(closedConnection, reason); }); this.server.on("message", (message, connection) => { @@ -352,7 +352,7 @@ export class Bridge { } async stopInstance(instance: BridgeClientConnection, recluster = true) { - if(this.eventMap.CLIENT_STOP) this.eventMap.CLIENT_STOP(instance); + if(this.eventMap.HOST_STOP) this.eventMap.HOST_STOP(instance); instance.connectionStatus = BridgeClientConnectionStatus.PENDING_STOP; let clusterToSteal: BridgeClientCluster | undefined; @@ -448,8 +448,8 @@ export type BridgeEventListeners = { 'CLUSTER_SPAWNED': ((cluster: BridgeClientCluster, connection: BridgeClientConnection) => void) | undefined, 'CLUSTER_RECLUSTER': ((cluster: BridgeClientCluster, newConnection: BridgeClientConnection, oldConnection: BridgeClientConnection) => void) | undefined, 'CLUSTER_HEARTBEAT_FAILED': ((cluster: BridgeClientCluster, error: unknown) => void) | undefined, - 'CLIENT_CONNECTED': ((client: BridgeClientConnection) => void) | undefined, - 'CLIENT_DISCONNECTED': ((client: BridgeClientConnection, reason: string) => void) | undefined, + 'HOST_CONNECTED': ((client: BridgeClientConnection) => void) | undefined, + 'HOST_DISCONNECTED': ((client: BridgeClientConnection, reason: string) => void) | undefined, 'ERROR': ((error: string) => void) | undefined, - 'CLIENT_STOP': ((instance: BridgeClientConnection) => void) | undefined + 'HOST_STOP': ((instance: BridgeClientConnection) => void) | undefined }; \ No newline at end of file