UNPKG

galactic.ts

Version:

galactic is a scalable clustering and sharding framework for Discord bots, enabling efficient multi-machine deployments with seamless discord.js integration.

328 lines (314 loc) 14.5 kB
import { Connection, Server } from 'net-ipc'; import { GatewayIntentsString, 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<void>, on: (message: unknown) => void, request: (message: unknown) => unknown); send(data: unknown): Promise<void>; request<T>(payload: unknown, timeout: number): Promise<T>; 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; 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<string, BridgeClientConnection>; private readonly token; private readonly intents; private readonly shardsPerCluster; private readonly clusterToStart; private readonly clusterCalculator; private readonly eventMap; constructor(port: number, token: string, intents: GatewayIntentsString[], shardsPerCluster: number, clusterToStart: number); start(): void; private interval; private checkRecluster; private heartbeat; private checkCreate; private createCluster; startListening(): void; sendMessageToClient(clientId: string, message: unknown): void; private getTotalShards; on<K extends keyof BridgeEventListeners>(event: K, listener: BridgeEventListeners[K]): void; getClusters(): BridgeClientCluster[]; stopAllInstances(): Promise<void>; stopAllInstancesWithRestart(): Promise<void>; moveCluster(instance: BridgeClientConnection, cluster: BridgeClientCluster): Promise<void>; stopInstance(instance: BridgeClientConnection, recluster?: boolean): Promise<void>; } 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<string, BridgeClientConnection>): BridgeClientConnection | undefined; getClusterOfShard(shardID: number): BridgeClientCluster | undefined; } declare class Cluster<T extends Client> { readonly instanceID: number; readonly clusterID: number; readonly shardList: number[]; readonly totalShards: number; readonly token: string; readonly intents: GatewayIntentsString[]; eventManager: EventManager; client: T; private readonly eventMap; constructor(instanceID: number, clusterID: number, shardList: number[], totalShards: number, token: string, intents: GatewayIntentsString[]); static initial<T extends Client>(): Cluster<T>; triggerReady(guilds: number, members: number): void; triggerError(e: any): void; private wait; private _onMessage; private _onRequest; on<K extends keyof ClusterEventListeners>(event: K, listener: ClusterEventListeners[K]): void; sendMessage(data: unknown): void; sendRequest(data: unknown, timeout?: number): Promise<unknown>; broadcastEval<Result>(fn: (cluster: T) => Result, timeout?: number): Promise<Result[]>; sendMessageToClusterOfGuild(guildID: string, message: unknown): void; sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout?: number): Promise<unknown>; } 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<unknown>; } declare class ShardingUtil { static getShardIDForGuild(guildID: string, totalShards: number): number; } declare abstract class BotInstance { private readonly entryPoint; private readonly execArgv; readonly clients: Map<number, ClusterProcess>; 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<unknown>; on<K extends keyof BotInstanceEventListeners>(event: K, listener: BotInstanceEventListeners[K]): void; sendRequestToClusterOfGuild(guildID: string, message: unknown, timeout?: number): Promise<unknown>; sendRequestToCluster(cluster: ClusterProcess, message: unknown, timeout?: number): Promise<unknown>; } 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_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<unknown>; 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<unknown>; } 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 };