galactic.ts 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/index.d.mts +327 -0
- package/dist/index.d.ts +327 -0
- package/dist/index.js +1544 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1494 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +50 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1494 @@
|
|
|
1
|
+
// src/bridge/BridgeClientCluster.ts
|
|
2
|
+
var BridgeClientClusterConnectionStatus = /* @__PURE__ */ ((BridgeClientClusterConnectionStatus2) => {
|
|
3
|
+
BridgeClientClusterConnectionStatus2["REQUESTING"] = "requesting";
|
|
4
|
+
BridgeClientClusterConnectionStatus2["STARTING"] = "starting";
|
|
5
|
+
BridgeClientClusterConnectionStatus2["CONNECTED"] = "connected";
|
|
6
|
+
BridgeClientClusterConnectionStatus2["RECLUSTERING"] = "reclustering";
|
|
7
|
+
BridgeClientClusterConnectionStatus2["DISCONNECTED"] = "disconnected";
|
|
8
|
+
return BridgeClientClusterConnectionStatus2;
|
|
9
|
+
})(BridgeClientClusterConnectionStatus || {});
|
|
10
|
+
var BridgeClientCluster = class {
|
|
11
|
+
clusterID;
|
|
12
|
+
shardList;
|
|
13
|
+
connectionStatus = "disconnected" /* DISCONNECTED */;
|
|
14
|
+
connection;
|
|
15
|
+
oldConnection;
|
|
16
|
+
missedHeartbeats = 0;
|
|
17
|
+
heartbeatResponse;
|
|
18
|
+
heartbeatPending = false;
|
|
19
|
+
startedAt;
|
|
20
|
+
constructor(clusterID, shardList) {
|
|
21
|
+
this.clusterID = clusterID;
|
|
22
|
+
this.shardList = shardList;
|
|
23
|
+
}
|
|
24
|
+
setConnection(connection) {
|
|
25
|
+
if (connection == void 0) {
|
|
26
|
+
this.connectionStatus = "disconnected" /* DISCONNECTED */;
|
|
27
|
+
this.connection = void 0;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (this.connection) {
|
|
31
|
+
throw new Error(`Connection already set for cluster ${this.clusterID}`);
|
|
32
|
+
}
|
|
33
|
+
this.connectionStatus = "requesting" /* REQUESTING */;
|
|
34
|
+
this.connection = connection;
|
|
35
|
+
}
|
|
36
|
+
setOldConnection(connection) {
|
|
37
|
+
this.oldConnection = connection;
|
|
38
|
+
}
|
|
39
|
+
isUsed() {
|
|
40
|
+
return this.connection != void 0 && this.connectionStatus !== "disconnected" /* DISCONNECTED */;
|
|
41
|
+
}
|
|
42
|
+
reclustering(connection) {
|
|
43
|
+
this.connectionStatus = "reclustering" /* RECLUSTERING */;
|
|
44
|
+
this.oldConnection = this.connection;
|
|
45
|
+
this.connection = connection;
|
|
46
|
+
}
|
|
47
|
+
addMissedHeartbeat() {
|
|
48
|
+
this.missedHeartbeats++;
|
|
49
|
+
}
|
|
50
|
+
removeMissedHeartbeat() {
|
|
51
|
+
if (this.missedHeartbeats > 0) {
|
|
52
|
+
this.missedHeartbeats--;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
resetMissedHeartbeats() {
|
|
56
|
+
this.missedHeartbeats = 0;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/general/EventManager.ts
|
|
61
|
+
var EventManager = class {
|
|
62
|
+
pendingPayloads = /* @__PURE__ */ new Map();
|
|
63
|
+
// Track per-request timeout handles so we can clear them on resolve/reject
|
|
64
|
+
pendingTimeouts = /* @__PURE__ */ new Map();
|
|
65
|
+
_send;
|
|
66
|
+
_on;
|
|
67
|
+
_request;
|
|
68
|
+
constructor(send, on, request) {
|
|
69
|
+
this._send = send;
|
|
70
|
+
this._on = on;
|
|
71
|
+
this._request = request;
|
|
72
|
+
}
|
|
73
|
+
async send(data) {
|
|
74
|
+
return this._send({
|
|
75
|
+
id: crypto.randomUUID(),
|
|
76
|
+
type: "message",
|
|
77
|
+
data
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async request(payload, timeout) {
|
|
81
|
+
const id = crypto.randomUUID();
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
this._send({
|
|
84
|
+
id,
|
|
85
|
+
type: "request",
|
|
86
|
+
data: payload
|
|
87
|
+
});
|
|
88
|
+
this.pendingPayloads.set(id, {
|
|
89
|
+
resolve,
|
|
90
|
+
reject
|
|
91
|
+
});
|
|
92
|
+
const t = setTimeout(() => {
|
|
93
|
+
if (this.pendingPayloads.has(id)) {
|
|
94
|
+
this.pendingPayloads.delete(id);
|
|
95
|
+
this.pendingTimeouts.delete(id);
|
|
96
|
+
reject({
|
|
97
|
+
error: `Request with id ${id} timed out`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}, timeout);
|
|
101
|
+
this.pendingTimeouts.set(id, t);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
receive(possiblePayload) {
|
|
105
|
+
if (typeof possiblePayload !== "object" || possiblePayload === null) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const payload = possiblePayload;
|
|
109
|
+
if (!payload.id || !payload.type) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (payload.type === "message") {
|
|
113
|
+
this._on(payload.data);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (payload.type === "response") {
|
|
117
|
+
const resolve = this.pendingPayloads.get(payload.id)?.resolve;
|
|
118
|
+
if (resolve) {
|
|
119
|
+
resolve(payload.data);
|
|
120
|
+
this.pendingPayloads.delete(payload.id);
|
|
121
|
+
const to = this.pendingTimeouts.get(payload.id);
|
|
122
|
+
if (to) clearTimeout(to);
|
|
123
|
+
this.pendingTimeouts.delete(payload.id);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (payload.type === "response_error") {
|
|
128
|
+
const reject = this.pendingPayloads.get(payload.id)?.reject;
|
|
129
|
+
if (reject) {
|
|
130
|
+
reject(payload.data);
|
|
131
|
+
this.pendingPayloads.delete(payload.id);
|
|
132
|
+
const to = this.pendingTimeouts.get(payload.id);
|
|
133
|
+
if (to) clearTimeout(to);
|
|
134
|
+
this.pendingTimeouts.delete(payload.id);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (payload.type === "request") {
|
|
139
|
+
const data = this._request(payload.data);
|
|
140
|
+
if (data instanceof Promise) {
|
|
141
|
+
data.then((result2) => {
|
|
142
|
+
this._send({
|
|
143
|
+
id: payload.id,
|
|
144
|
+
type: "response",
|
|
145
|
+
data: result2
|
|
146
|
+
});
|
|
147
|
+
}).catch((error) => {
|
|
148
|
+
this._send({
|
|
149
|
+
id: payload.id,
|
|
150
|
+
type: "response_error",
|
|
151
|
+
data: error
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
} else {
|
|
155
|
+
this._send({
|
|
156
|
+
id: payload.id,
|
|
157
|
+
type: "response",
|
|
158
|
+
data
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Reject and clear all pending requests to avoid memory leaks when a connection/process closes
|
|
165
|
+
close(reason) {
|
|
166
|
+
if (this.pendingPayloads.size === 0 && this.pendingTimeouts.size === 0) return;
|
|
167
|
+
const err = { error: reason || "EventManager closed" };
|
|
168
|
+
for (const [id, handlers] of this.pendingPayloads.entries()) {
|
|
169
|
+
try {
|
|
170
|
+
handlers.reject(err);
|
|
171
|
+
} catch (_) {
|
|
172
|
+
}
|
|
173
|
+
this.pendingPayloads.delete(id);
|
|
174
|
+
const to = this.pendingTimeouts.get(id);
|
|
175
|
+
if (to) clearTimeout(to);
|
|
176
|
+
this.pendingTimeouts.delete(id);
|
|
177
|
+
}
|
|
178
|
+
for (const to of this.pendingTimeouts.values()) {
|
|
179
|
+
clearTimeout(to);
|
|
180
|
+
}
|
|
181
|
+
this.pendingTimeouts.clear();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// src/bridge/BridgeClientConnection.ts
|
|
186
|
+
var BridgeClientConnectionStatus = /* @__PURE__ */ ((BridgeClientConnectionStatus2) => {
|
|
187
|
+
BridgeClientConnectionStatus2["READY"] = "ready";
|
|
188
|
+
BridgeClientConnectionStatus2["PENDING_STOP"] = "pending_stop";
|
|
189
|
+
return BridgeClientConnectionStatus2;
|
|
190
|
+
})(BridgeClientConnectionStatus || {});
|
|
191
|
+
var BridgeClientConnection = class {
|
|
192
|
+
instanceID;
|
|
193
|
+
eventManager;
|
|
194
|
+
connection;
|
|
195
|
+
data;
|
|
196
|
+
connectionStatus = "ready" /* READY */;
|
|
197
|
+
dev = false;
|
|
198
|
+
_onMessage;
|
|
199
|
+
_onRequest;
|
|
200
|
+
constructor(instanceID, connection, data, dev) {
|
|
201
|
+
this.instanceID = instanceID;
|
|
202
|
+
this.connection = connection;
|
|
203
|
+
this.data = data;
|
|
204
|
+
this.dev = dev || false;
|
|
205
|
+
this.eventManager = new EventManager((message2) => {
|
|
206
|
+
if (!this.connection?.connection?.closed) {
|
|
207
|
+
return this.connection.send(message2);
|
|
208
|
+
}
|
|
209
|
+
return Promise.reject(new Error("Connection is closed, cannot send message"));
|
|
210
|
+
}, (message2) => {
|
|
211
|
+
if (this._onMessage) {
|
|
212
|
+
this._onMessage(message2);
|
|
213
|
+
}
|
|
214
|
+
}, (message2) => {
|
|
215
|
+
if (this._onRequest) {
|
|
216
|
+
return this._onRequest(message2);
|
|
217
|
+
}
|
|
218
|
+
return void 0;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
messageReceive(message2) {
|
|
222
|
+
this.eventManager.receive(message2);
|
|
223
|
+
}
|
|
224
|
+
onRequest(callback) {
|
|
225
|
+
this._onRequest = callback;
|
|
226
|
+
}
|
|
227
|
+
onMessage(callback) {
|
|
228
|
+
this._onMessage = callback;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// src/bridge/Bridge.ts
|
|
233
|
+
import { Server } from "net-ipc";
|
|
234
|
+
|
|
235
|
+
// src/bridge/ClusterCalculator.ts
|
|
236
|
+
var ClusterCalculator = class {
|
|
237
|
+
/** The total number of clusters to initialize */
|
|
238
|
+
clusterToStart;
|
|
239
|
+
/** The number of shards that each cluster will manage */
|
|
240
|
+
shardsPerCluster;
|
|
241
|
+
/** List of all clusters managed by this calculator */
|
|
242
|
+
clusterList = [];
|
|
243
|
+
/**
|
|
244
|
+
* Creates a new ClusterCalculator and initializes the clusters.
|
|
245
|
+
*
|
|
246
|
+
* @param clusterToStart - The number of clusters to create
|
|
247
|
+
* @param shardsPerCluster - The number of shards each cluster will manage
|
|
248
|
+
*/
|
|
249
|
+
constructor(clusterToStart, shardsPerCluster) {
|
|
250
|
+
this.shardsPerCluster = shardsPerCluster;
|
|
251
|
+
this.clusterToStart = clusterToStart;
|
|
252
|
+
this.calculateClusters();
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Calculates and initializes all clusters with their assigned shards.
|
|
256
|
+
* Each cluster is assigned a sequential range of shard IDs based on its cluster index.
|
|
257
|
+
*/
|
|
258
|
+
calculateClusters() {
|
|
259
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
260
|
+
for (let i = 0; i < this.clusterToStart; i++) {
|
|
261
|
+
clusters.set(i, []);
|
|
262
|
+
for (let j = 0; j < this.shardsPerCluster; j++) {
|
|
263
|
+
clusters.get(i)?.push(i * this.shardsPerCluster + j);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
for (let [clusterIndex, clusterShards] of clusters.entries()) {
|
|
267
|
+
this.clusterList.push(new BridgeClientCluster(clusterIndex, clusterShards));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Retrieves the next available (unused) cluster and marks it as used.
|
|
272
|
+
*
|
|
273
|
+
* @returns The next available cluster, or undefined if all clusters are in use
|
|
274
|
+
*/
|
|
275
|
+
getNextCluster() {
|
|
276
|
+
for (const cluster of this.clusterList) {
|
|
277
|
+
if (!cluster.isUsed()) {
|
|
278
|
+
return cluster;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Retrieves multiple available clusters up to the specified count.
|
|
285
|
+
* Each returned cluster is marked as used.
|
|
286
|
+
*
|
|
287
|
+
* @param count - The maximum number of clusters to retrieve
|
|
288
|
+
* @returns An array of available clusters (may be fewer than requested if not enough are available)
|
|
289
|
+
*/
|
|
290
|
+
getNextClusters(count) {
|
|
291
|
+
const availableClusters = [];
|
|
292
|
+
for (const cluster of this.clusterList) {
|
|
293
|
+
if (!cluster.isUsed() && availableClusters.length < count) {
|
|
294
|
+
availableClusters.push(cluster);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return availableClusters;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Sets the used status of a specific cluster by its ID.
|
|
301
|
+
*
|
|
302
|
+
* @param clusterID - The ID of the cluster to update
|
|
303
|
+
* @param connection - The connection to associate with the cluster
|
|
304
|
+
*/
|
|
305
|
+
clearClusterConnection(clusterID) {
|
|
306
|
+
const cluster = this.clusterList.find((c) => c.clusterID === clusterID);
|
|
307
|
+
if (cluster) {
|
|
308
|
+
cluster.setConnection(void 0);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
getClusterForConnection(connection) {
|
|
312
|
+
return this.clusterList.filter(
|
|
313
|
+
(cluster) => cluster.connection?.instanceID === connection.instanceID
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
getOldClusterForConnection(connection) {
|
|
317
|
+
return this.clusterList.filter(
|
|
318
|
+
(cluster) => cluster.oldConnection?.instanceID === connection.instanceID
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
checkAllClustersConnected() {
|
|
322
|
+
for (const cluster of this.clusterList) {
|
|
323
|
+
if (cluster.connectionStatus != "connected" /* CONNECTED */) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
findMostAndLeastClustersForConnections(connectedClients) {
|
|
330
|
+
const openClients = connectedClients.filter((x) => !x.dev);
|
|
331
|
+
const devClients = connectedClients.filter((x) => x.dev);
|
|
332
|
+
const summDevConnectedClusters = devClients.map((c) => this.getClusterForConnection(c).length).reduce((a, b) => a + b, 0);
|
|
333
|
+
let most;
|
|
334
|
+
let least;
|
|
335
|
+
let remainder = (this.clusterToStart - summDevConnectedClusters) % openClients.length || 0;
|
|
336
|
+
for (const client of openClients) {
|
|
337
|
+
const clusters = this.getClusterForConnection(client);
|
|
338
|
+
if (!most || clusters.length > this.getClusterForConnection(most).length) {
|
|
339
|
+
most = client;
|
|
340
|
+
}
|
|
341
|
+
if (!least || clusters.length < this.getClusterForConnection(least).length) {
|
|
342
|
+
least = client;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (most && least) {
|
|
346
|
+
const mostCount = this.getClusterForConnection(most).length;
|
|
347
|
+
const leastCount = this.getClusterForConnection(least).length;
|
|
348
|
+
if (mostCount - leastCount <= remainder) {
|
|
349
|
+
return { most: void 0, least: void 0 };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return { most, least };
|
|
353
|
+
}
|
|
354
|
+
getClusterWithLowestLoad(connectedClients) {
|
|
355
|
+
let lowestLoadClient;
|
|
356
|
+
let lowestLoad = Infinity;
|
|
357
|
+
for (const client of connectedClients.values().filter((c) => c.connectionStatus === "ready" /* READY */ && !c.dev)) {
|
|
358
|
+
const clusters = this.getClusterForConnection(client);
|
|
359
|
+
const load = clusters.length;
|
|
360
|
+
if (load < lowestLoad) {
|
|
361
|
+
lowestLoad = load;
|
|
362
|
+
lowestLoadClient = client;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return lowestLoadClient;
|
|
366
|
+
}
|
|
367
|
+
getClusterOfShard(shardID) {
|
|
368
|
+
return this.clusterList.find((c) => c.shardList.includes(shardID));
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// src/general/ShardingUtil.ts
|
|
373
|
+
var ShardingUtil = class {
|
|
374
|
+
static getShardIDForGuild(guildID, totalShards) {
|
|
375
|
+
if (!guildID || totalShards <= 0) {
|
|
376
|
+
throw new Error("Invalid guild ID or total shards");
|
|
377
|
+
}
|
|
378
|
+
return Number(BigInt(guildID) >> 22n) % totalShards;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// src/bridge/Bridge.ts
|
|
383
|
+
var Bridge = class {
|
|
384
|
+
port;
|
|
385
|
+
server;
|
|
386
|
+
connectedClients = /* @__PURE__ */ new Map();
|
|
387
|
+
token;
|
|
388
|
+
intents;
|
|
389
|
+
shardsPerCluster = 1;
|
|
390
|
+
clusterToStart = 1;
|
|
391
|
+
clusterCalculator;
|
|
392
|
+
eventMap = {
|
|
393
|
+
CLUSTER_READY: void 0,
|
|
394
|
+
CLUSTER_HEARTBEAT_FAILED: void 0,
|
|
395
|
+
CLUSTER_STOPPED: void 0,
|
|
396
|
+
CLIENT_CONNECTED: void 0,
|
|
397
|
+
CLIENT_DISCONNECTED: void 0,
|
|
398
|
+
CLUSTER_SPAWNED: void 0,
|
|
399
|
+
CLUSTER_RECLUSTER: void 0,
|
|
400
|
+
ERROR: void 0,
|
|
401
|
+
CLIENT_STOP: void 0
|
|
402
|
+
};
|
|
403
|
+
constructor(port, token, intents, shardsPerCluster, clusterToStart) {
|
|
404
|
+
this.port = port;
|
|
405
|
+
this.token = token;
|
|
406
|
+
this.intents = intents;
|
|
407
|
+
this.clusterToStart = clusterToStart;
|
|
408
|
+
this.shardsPerCluster = shardsPerCluster;
|
|
409
|
+
this.clusterCalculator = new ClusterCalculator(this.clusterToStart, this.shardsPerCluster);
|
|
410
|
+
this.server = new Server({
|
|
411
|
+
port: this.port
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
start() {
|
|
415
|
+
this.server.start().then(() => {
|
|
416
|
+
this.startListening();
|
|
417
|
+
});
|
|
418
|
+
this.interval();
|
|
419
|
+
}
|
|
420
|
+
interval() {
|
|
421
|
+
setInterval(() => {
|
|
422
|
+
this.checkCreate();
|
|
423
|
+
this.checkRecluster();
|
|
424
|
+
this.heartbeat();
|
|
425
|
+
}, 5e3);
|
|
426
|
+
}
|
|
427
|
+
checkRecluster() {
|
|
428
|
+
const up = this.clusterCalculator.checkAllClustersConnected();
|
|
429
|
+
if (!up) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const connectedClients = this.connectedClients.values().filter((c) => c.connectionStatus == "ready" /* READY */ && !c.dev).toArray();
|
|
433
|
+
const { most, least } = this.clusterCalculator.findMostAndLeastClustersForConnections(connectedClients);
|
|
434
|
+
if (most) {
|
|
435
|
+
const clusterToSteal = this.clusterCalculator.getClusterForConnection(most)[0] || void 0;
|
|
436
|
+
if (least && clusterToSteal) {
|
|
437
|
+
clusterToSteal.reclustering(least);
|
|
438
|
+
if (this.eventMap.CLUSTER_RECLUSTER) this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection);
|
|
439
|
+
this.createCluster(least, clusterToSteal, true);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
heartbeat() {
|
|
445
|
+
const clusters = this.clusterCalculator.clusterList;
|
|
446
|
+
clusters.forEach((cluster) => {
|
|
447
|
+
if (cluster.connection && cluster.connectionStatus == "connected" /* CONNECTED */ && !cluster.heartbeatPending) {
|
|
448
|
+
cluster.heartbeatPending = true;
|
|
449
|
+
cluster.connection.eventManager.request({
|
|
450
|
+
type: "CLUSTER_HEARTBEAT",
|
|
451
|
+
data: {
|
|
452
|
+
clusterID: cluster.clusterID
|
|
453
|
+
}
|
|
454
|
+
}, 2e4).then((r) => {
|
|
455
|
+
cluster.removeMissedHeartbeat();
|
|
456
|
+
cluster.heartbeatResponse = r;
|
|
457
|
+
}).catch((err) => {
|
|
458
|
+
if (this.eventMap.CLUSTER_HEARTBEAT_FAILED) this.eventMap.CLUSTER_HEARTBEAT_FAILED(cluster, err);
|
|
459
|
+
cluster.addMissedHeartbeat();
|
|
460
|
+
if (cluster.missedHeartbeats > 7 && !cluster.connection?.dev) {
|
|
461
|
+
cluster.connection?.eventManager.send({
|
|
462
|
+
type: "CLUSTER_STOP",
|
|
463
|
+
data: {
|
|
464
|
+
id: cluster.clusterID
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
cluster.connectionStatus = "disconnected" /* DISCONNECTED */;
|
|
468
|
+
cluster.resetMissedHeartbeats();
|
|
469
|
+
}
|
|
470
|
+
}).finally(() => {
|
|
471
|
+
cluster.heartbeatPending = false;
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
checkCreate() {
|
|
477
|
+
const optionalCluster = this.clusterCalculator.getNextCluster();
|
|
478
|
+
if (!optionalCluster) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const lowestLoadClient = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients);
|
|
482
|
+
if (!lowestLoadClient) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
this.createCluster(lowestLoadClient, optionalCluster);
|
|
486
|
+
}
|
|
487
|
+
createCluster(connection, cluster, recluster = false) {
|
|
488
|
+
cluster.resetMissedHeartbeats();
|
|
489
|
+
cluster.heartbeatResponse = void 0;
|
|
490
|
+
if (!recluster) {
|
|
491
|
+
cluster.setConnection(connection);
|
|
492
|
+
} else {
|
|
493
|
+
cluster.oldConnection?.eventManager.send({
|
|
494
|
+
type: "CLUSTER_RECLUSTER",
|
|
495
|
+
data: {
|
|
496
|
+
clusterID: cluster.clusterID
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
if (this.eventMap.CLUSTER_SPAWNED) this.eventMap.CLUSTER_SPAWNED(cluster, connection);
|
|
501
|
+
connection.eventManager.send({
|
|
502
|
+
type: "CLUSTER_CREATE",
|
|
503
|
+
data: {
|
|
504
|
+
clusterID: cluster.clusterID,
|
|
505
|
+
instanceID: connection.instanceID,
|
|
506
|
+
totalShards: this.getTotalShards(),
|
|
507
|
+
shardList: cluster.shardList,
|
|
508
|
+
token: this.token,
|
|
509
|
+
intents: this.intents
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
startListening() {
|
|
514
|
+
this.server.on("connect", (connection, payload) => {
|
|
515
|
+
const id = payload?.id;
|
|
516
|
+
const data = payload.data;
|
|
517
|
+
const dev = payload?.dev || false;
|
|
518
|
+
if (!id) {
|
|
519
|
+
connection.close("Invalid payload", false);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (this.connectedClients.values().some((client) => client.instanceID === id)) {
|
|
523
|
+
connection.close("Already connected", false);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const bridgeConnection = new BridgeClientConnection(payload.id, connection, data, dev);
|
|
527
|
+
if (this.eventMap.CLIENT_CONNECTED) this.eventMap.CLIENT_CONNECTED(bridgeConnection);
|
|
528
|
+
bridgeConnection.onMessage((m2) => {
|
|
529
|
+
if (m2.type == "CLUSTER_SPAWNED") {
|
|
530
|
+
const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id);
|
|
531
|
+
if (cluster) {
|
|
532
|
+
cluster.connectionStatus = "starting" /* STARTING */;
|
|
533
|
+
}
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (m2.type == "CLUSTER_READY") {
|
|
537
|
+
const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id);
|
|
538
|
+
if (cluster) {
|
|
539
|
+
cluster.startedAt = Date.now();
|
|
540
|
+
if (this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(cluster, m2.data.guilds || 0, m2.data.members || 0);
|
|
541
|
+
cluster.connectionStatus = "connected" /* CONNECTED */;
|
|
542
|
+
if (cluster.oldConnection) {
|
|
543
|
+
cluster.oldConnection.eventManager.send({
|
|
544
|
+
type: "CLUSTER_STOP",
|
|
545
|
+
data: {
|
|
546
|
+
id: cluster.clusterID
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
cluster.oldConnection = void 0;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (m2.type == "CLUSTER_STOPPED") {
|
|
555
|
+
const cluster = this.clusterCalculator.getClusterForConnection(bridgeConnection).find((c) => c.clusterID === m2.data.id);
|
|
556
|
+
if (cluster) {
|
|
557
|
+
cluster.startedAt = void 0;
|
|
558
|
+
if (this.eventMap.CLUSTER_STOPPED) this.eventMap.CLUSTER_STOPPED(cluster);
|
|
559
|
+
cluster.setConnection(void 0);
|
|
560
|
+
}
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (m2.type == "INSTANCE_STOP") {
|
|
564
|
+
this.stopInstance(bridgeConnection);
|
|
565
|
+
}
|
|
566
|
+
return;
|
|
567
|
+
});
|
|
568
|
+
bridgeConnection.onRequest((m2) => {
|
|
569
|
+
if (m2.type == "REDIRECT_REQUEST_TO_GUILD") {
|
|
570
|
+
const guildID = m2.guildID;
|
|
571
|
+
const shardID = ShardingUtil.getShardIDForGuild(guildID, this.getTotalShards());
|
|
572
|
+
const cluster = this.clusterCalculator.getClusterOfShard(shardID);
|
|
573
|
+
if (!cluster) {
|
|
574
|
+
return Promise.reject(new Error("cluster not found"));
|
|
575
|
+
}
|
|
576
|
+
if (cluster.connectionStatus != "connected" /* CONNECTED */) {
|
|
577
|
+
return Promise.reject(new Error("cluster not connected."));
|
|
578
|
+
}
|
|
579
|
+
if (!cluster.connection?.eventManager) {
|
|
580
|
+
return Promise.reject(new Error("no connection defined."));
|
|
581
|
+
}
|
|
582
|
+
return cluster.connection.eventManager.request({
|
|
583
|
+
type: "REDIRECT_REQUEST_TO_GUILD",
|
|
584
|
+
clusterID: cluster.clusterID,
|
|
585
|
+
guildID,
|
|
586
|
+
data: m2.data
|
|
587
|
+
}, 5e3);
|
|
588
|
+
}
|
|
589
|
+
if (m2.type == "BROADCAST_EVAL") {
|
|
590
|
+
const responses = Promise.all(
|
|
591
|
+
this.connectedClients.values().map((c) => {
|
|
592
|
+
return c.eventManager.request({
|
|
593
|
+
type: "BROADCAST_EVAL",
|
|
594
|
+
data: m2.data
|
|
595
|
+
}, 5e3);
|
|
596
|
+
})
|
|
597
|
+
);
|
|
598
|
+
return new Promise((resolve, reject) => {
|
|
599
|
+
responses.then((r) => {
|
|
600
|
+
resolve(r.flatMap((f) => f));
|
|
601
|
+
}).catch(reject);
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
if (m2.type == "SELF_CHECK") {
|
|
605
|
+
return {
|
|
606
|
+
clusterList: [
|
|
607
|
+
...this.clusterCalculator.getClusterForConnection(bridgeConnection).map((c) => c.clusterID),
|
|
608
|
+
...this.clusterCalculator.getOldClusterForConnection(bridgeConnection).map((c) => c.clusterID)
|
|
609
|
+
]
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
return Promise.reject(new Error("unknown type"));
|
|
613
|
+
});
|
|
614
|
+
this.connectedClients.set(connection.id, bridgeConnection);
|
|
615
|
+
});
|
|
616
|
+
this.server.on("disconnect", (connection, reason) => {
|
|
617
|
+
const closedConnection = this.connectedClients.get(connection.id);
|
|
618
|
+
if (!closedConnection) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const clusters = this.clusterCalculator.getClusterForConnection(closedConnection);
|
|
622
|
+
for (const cluster of clusters) {
|
|
623
|
+
this.clusterCalculator.clearClusterConnection(cluster.clusterID);
|
|
624
|
+
}
|
|
625
|
+
this.connectedClients.delete(connection.id);
|
|
626
|
+
if (this.eventMap.CLIENT_DISCONNECTED) this.eventMap.CLIENT_DISCONNECTED(closedConnection, reason);
|
|
627
|
+
});
|
|
628
|
+
this.server.on("message", (message2, connection) => {
|
|
629
|
+
this.sendMessageToClient(connection.id, message2);
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
sendMessageToClient(clientId, message2) {
|
|
633
|
+
if (!this.connectedClients.has(clientId)) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const client = this.connectedClients.get(clientId);
|
|
637
|
+
if (client) {
|
|
638
|
+
client.messageReceive(message2);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
getTotalShards() {
|
|
642
|
+
return this.shardsPerCluster * this.clusterToStart;
|
|
643
|
+
}
|
|
644
|
+
on(event, listener) {
|
|
645
|
+
this.eventMap[event] = listener;
|
|
646
|
+
}
|
|
647
|
+
getClusters() {
|
|
648
|
+
return this.clusterCalculator.clusterList;
|
|
649
|
+
}
|
|
650
|
+
async stopAllInstances() {
|
|
651
|
+
const instances = Array.from(this.connectedClients.values());
|
|
652
|
+
for (const instance of instances) {
|
|
653
|
+
instance.connectionStatus = "pending_stop" /* PENDING_STOP */;
|
|
654
|
+
}
|
|
655
|
+
for (const instance of instances) {
|
|
656
|
+
await this.stopInstance(instance, false);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
async stopAllInstancesWithRestart() {
|
|
660
|
+
const instances = Array.from(this.connectedClients.values());
|
|
661
|
+
for (const instance of instances) {
|
|
662
|
+
await this.stopInstance(instance);
|
|
663
|
+
await new Promise((resolve) => {
|
|
664
|
+
setTimeout(async () => {
|
|
665
|
+
resolve();
|
|
666
|
+
}, 1e3 * 10);
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async moveCluster(instance, cluster) {
|
|
671
|
+
cluster.reclustering(instance);
|
|
672
|
+
this.createCluster(instance, cluster, true);
|
|
673
|
+
}
|
|
674
|
+
async stopInstance(instance, recluster = true) {
|
|
675
|
+
if (this.eventMap.CLIENT_STOP) this.eventMap.CLIENT_STOP(instance);
|
|
676
|
+
instance.connectionStatus = "pending_stop" /* PENDING_STOP */;
|
|
677
|
+
let clusterToSteal;
|
|
678
|
+
await instance.eventManager.send({
|
|
679
|
+
type: "INSTANCE_STOP"
|
|
680
|
+
});
|
|
681
|
+
if (recluster) {
|
|
682
|
+
while ((clusterToSteal = this.clusterCalculator.getClusterForConnection(instance).filter((c) => c.connectionStatus === "connected" /* CONNECTED */ || c.connectionStatus == "starting" /* STARTING */ || c.connectionStatus == "reclustering" /* RECLUSTERING */)[0]) !== void 0) {
|
|
683
|
+
if (clusterToSteal.connectionStatus != "connected" /* CONNECTED */) break;
|
|
684
|
+
const least = this.clusterCalculator.getClusterWithLowestLoad(this.connectedClients);
|
|
685
|
+
if (!least) {
|
|
686
|
+
if (this.eventMap.ERROR) {
|
|
687
|
+
this.eventMap.ERROR("Reclustering failed: No least cluster found.");
|
|
688
|
+
}
|
|
689
|
+
await instance.eventManager.send({
|
|
690
|
+
type: "CLUSTER_STOP",
|
|
691
|
+
data: {
|
|
692
|
+
id: clusterToSteal.clusterID
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
clusterToSteal.connection = void 0;
|
|
696
|
+
clusterToSteal.connectionStatus = "disconnected" /* DISCONNECTED */;
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
clusterToSteal.reclustering(least);
|
|
700
|
+
if (this.eventMap.CLUSTER_RECLUSTER) {
|
|
701
|
+
this.eventMap.CLUSTER_RECLUSTER(clusterToSteal, least, clusterToSteal.oldConnection);
|
|
702
|
+
}
|
|
703
|
+
this.createCluster(least, clusterToSteal, true);
|
|
704
|
+
}
|
|
705
|
+
return new Promise((resolve, reject) => {
|
|
706
|
+
const interval = setInterval(async () => {
|
|
707
|
+
const cluster = this.clusterCalculator.getOldClusterForConnection(instance)[0] || void 0;
|
|
708
|
+
if (!cluster) {
|
|
709
|
+
clearInterval(interval);
|
|
710
|
+
await instance.eventManager.send({
|
|
711
|
+
type: "INSTANCE_STOPPED"
|
|
712
|
+
});
|
|
713
|
+
await instance.connection.close("Instance stopped.", false);
|
|
714
|
+
resolve();
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
}, 1e3);
|
|
718
|
+
});
|
|
719
|
+
} else {
|
|
720
|
+
for (const cluster of this.clusterCalculator.getClusterForConnection(instance)) {
|
|
721
|
+
await instance.eventManager.send({
|
|
722
|
+
type: "CLUSTER_STOP",
|
|
723
|
+
data: {
|
|
724
|
+
id: cluster.clusterID
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
await instance.eventManager.send({
|
|
729
|
+
type: "INSTANCE_STOPPED"
|
|
730
|
+
});
|
|
731
|
+
await instance.connection.close("Instance stopped.", false);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
// src/cluster/Cluster.ts
|
|
737
|
+
import os from "os";
|
|
738
|
+
var Cluster = class _Cluster {
|
|
739
|
+
instanceID;
|
|
740
|
+
clusterID;
|
|
741
|
+
shardList = [];
|
|
742
|
+
totalShards;
|
|
743
|
+
token;
|
|
744
|
+
intents;
|
|
745
|
+
eventManager;
|
|
746
|
+
client;
|
|
747
|
+
eventMap = {
|
|
748
|
+
message: void 0,
|
|
749
|
+
request: void 0,
|
|
750
|
+
CLUSTER_READY: void 0
|
|
751
|
+
};
|
|
752
|
+
constructor(instanceID, clusterID, shardList, totalShards, token, intents) {
|
|
753
|
+
this.instanceID = instanceID;
|
|
754
|
+
this.clusterID = clusterID;
|
|
755
|
+
this.shardList = shardList;
|
|
756
|
+
this.totalShards = totalShards;
|
|
757
|
+
this.token = token;
|
|
758
|
+
this.intents = intents;
|
|
759
|
+
this.eventManager = new EventManager((message2) => {
|
|
760
|
+
return new Promise((resolve, reject) => {
|
|
761
|
+
if (typeof process.send !== "function") {
|
|
762
|
+
reject(new Error("Process does not support sending messages"));
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
process.send?.(message2, void 0, void 0, (error) => {
|
|
766
|
+
if (error) {
|
|
767
|
+
reject(error);
|
|
768
|
+
} else {
|
|
769
|
+
resolve();
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
}, (message2) => {
|
|
774
|
+
this._onMessage(message2);
|
|
775
|
+
}, (message2) => {
|
|
776
|
+
return this._onRequest(message2);
|
|
777
|
+
});
|
|
778
|
+
process.on("message", (message2) => {
|
|
779
|
+
this.eventManager.receive(message2);
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
static initial() {
|
|
783
|
+
const args = process.env;
|
|
784
|
+
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) {
|
|
785
|
+
throw new Error("Missing required environment variables");
|
|
786
|
+
}
|
|
787
|
+
const shardList = args.SHARD_LIST.split(",").map(Number);
|
|
788
|
+
const totalShards = Number(args.TOTAL_SHARDS);
|
|
789
|
+
const instanceID = Number(args.INSTANCE_ID);
|
|
790
|
+
const clusterID = Number(args.CLUSTER_ID);
|
|
791
|
+
const token = args.TOKEN;
|
|
792
|
+
const intents = args.INTENTS.split(",").map((i) => i.trim());
|
|
793
|
+
return new _Cluster(instanceID, clusterID, shardList, totalShards, token, intents);
|
|
794
|
+
}
|
|
795
|
+
triggerReady(guilds, members) {
|
|
796
|
+
this.eventManager.send({
|
|
797
|
+
type: "CLUSTER_READY",
|
|
798
|
+
id: this.clusterID,
|
|
799
|
+
guilds,
|
|
800
|
+
members
|
|
801
|
+
});
|
|
802
|
+
if (this.eventMap?.CLUSTER_READY) {
|
|
803
|
+
this.eventMap?.CLUSTER_READY();
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
triggerError(e) {
|
|
807
|
+
this.eventManager.send({
|
|
808
|
+
type: "CLUSTER_ERROR",
|
|
809
|
+
id: this.clusterID
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
async wait(ms) {
|
|
813
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
814
|
+
}
|
|
815
|
+
_onMessage(message2) {
|
|
816
|
+
const m2 = message2;
|
|
817
|
+
if (m2.type == "CUSTOM" && this.eventMap.message) {
|
|
818
|
+
this.eventMap.message(m2.data);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
_onRequest(message) {
|
|
822
|
+
const m = message;
|
|
823
|
+
if (m.type == "CUSTOM" && this.eventMap.request) {
|
|
824
|
+
return new Promise((resolve, reject) => {
|
|
825
|
+
this.eventMap.request(m.data, resolve, reject);
|
|
826
|
+
});
|
|
827
|
+
} else if (m.type == "CLUSTER_HEARTBEAT") {
|
|
828
|
+
const startTime = process.hrtime.bigint();
|
|
829
|
+
const startUsage = process.cpuUsage();
|
|
830
|
+
(async () => {
|
|
831
|
+
await this.wait(500);
|
|
832
|
+
})();
|
|
833
|
+
const endTime = process.hrtime.bigint();
|
|
834
|
+
const usageDiff = process.cpuUsage(startUsage);
|
|
835
|
+
const elapsedTimeUs = Number((endTime - startTime) / 1000n);
|
|
836
|
+
const totalCPUTime = usageDiff.user + usageDiff.system;
|
|
837
|
+
const cpuCount = os.cpus().length;
|
|
838
|
+
const cpuPercent = totalCPUTime / (elapsedTimeUs * cpuCount) * 100;
|
|
839
|
+
let shardPings = [];
|
|
840
|
+
try {
|
|
841
|
+
const shards = this.client.ws.shards;
|
|
842
|
+
if (shards) {
|
|
843
|
+
shards.forEach((shard) => {
|
|
844
|
+
shardPings.push({
|
|
845
|
+
id: shard.id,
|
|
846
|
+
ping: shard.ping,
|
|
847
|
+
status: shard.status,
|
|
848
|
+
guilds: this.client.guilds.cache.filter((g) => g.shardId === shard.id).size,
|
|
849
|
+
members: this.client.guilds.cache.filter((g) => g.shardId === shard.id).reduce((acc, g) => acc + g.memberCount, 0)
|
|
850
|
+
});
|
|
851
|
+
this.client.shard?.fetchClientValues("uptime", shard.id).then((values) => {
|
|
852
|
+
shardPings[shard.id]["uptime"] = values;
|
|
853
|
+
console.log(values);
|
|
854
|
+
}).catch((e) => {
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
} catch (_) {
|
|
859
|
+
}
|
|
860
|
+
return {
|
|
861
|
+
cpu: { raw: process.cpuUsage(), cpuPercent: cpuPercent.toFixed(2) },
|
|
862
|
+
memory: {
|
|
863
|
+
raw: process.memoryUsage(),
|
|
864
|
+
memoryPercent: (process.memoryUsage().heapUsed / process.memoryUsage().heapTotal * 100).toFixed(2) + "%",
|
|
865
|
+
usage: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + "MB"
|
|
866
|
+
},
|
|
867
|
+
ping: this.client.ws.ping,
|
|
868
|
+
shardPings
|
|
869
|
+
};
|
|
870
|
+
} else if (m.type == "BROADCAST_EVAL") {
|
|
871
|
+
const broadcast = message;
|
|
872
|
+
const fn = eval(`(${broadcast.data})`);
|
|
873
|
+
const result = fn(this.client);
|
|
874
|
+
if (result instanceof Promise) {
|
|
875
|
+
return new Promise((resolve, reject) => {
|
|
876
|
+
result.then((res) => {
|
|
877
|
+
resolve(res);
|
|
878
|
+
}).catch((err) => {
|
|
879
|
+
reject(err);
|
|
880
|
+
});
|
|
881
|
+
});
|
|
882
|
+
} else {
|
|
883
|
+
return result;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return void 0;
|
|
887
|
+
}
|
|
888
|
+
on(event, listener) {
|
|
889
|
+
this.eventMap[event] = listener;
|
|
890
|
+
}
|
|
891
|
+
sendMessage(data) {
|
|
892
|
+
this.eventManager.send({
|
|
893
|
+
type: "CUSTOM",
|
|
894
|
+
data
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
sendRequest(data, timeout = 5e3) {
|
|
898
|
+
return this.eventManager.request({
|
|
899
|
+
type: "CUSTOM",
|
|
900
|
+
data
|
|
901
|
+
}, timeout);
|
|
902
|
+
}
|
|
903
|
+
broadcastEval(fn2, timeout = 2e4) {
|
|
904
|
+
return this.eventManager.request({
|
|
905
|
+
type: "BROADCAST_EVAL",
|
|
906
|
+
data: fn2.toString()
|
|
907
|
+
}, timeout);
|
|
908
|
+
}
|
|
909
|
+
sendMessageToClusterOfGuild(guildID, message2) {
|
|
910
|
+
if (this.eventManager) {
|
|
911
|
+
this.eventManager.send({
|
|
912
|
+
type: "REDIRECT_MESSAGE_TO_GUILD",
|
|
913
|
+
guildID,
|
|
914
|
+
data: message2
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
sendRequestToClusterOfGuild(guildID, message2, timeout = 5e3) {
|
|
919
|
+
return new Promise((resolve, reject) => {
|
|
920
|
+
if (this.eventManager) {
|
|
921
|
+
this.eventManager.request({
|
|
922
|
+
type: "REDIRECT_REQUEST_TO_GUILD",
|
|
923
|
+
guildID,
|
|
924
|
+
data: message2
|
|
925
|
+
}, timeout).then((response) => {
|
|
926
|
+
resolve(response);
|
|
927
|
+
}).catch((error) => {
|
|
928
|
+
reject(error);
|
|
929
|
+
});
|
|
930
|
+
} else {
|
|
931
|
+
reject(new Error("Event manager is not initialized"));
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
// src/cluster/ClusterProcess.ts
|
|
938
|
+
var ClusterProcess = class {
|
|
939
|
+
child;
|
|
940
|
+
eventManager;
|
|
941
|
+
id;
|
|
942
|
+
shardList;
|
|
943
|
+
totalShards;
|
|
944
|
+
status;
|
|
945
|
+
createdAt = Date.now();
|
|
946
|
+
_onMessage;
|
|
947
|
+
_onRequest;
|
|
948
|
+
constructor(id, child, shardList, totalShards) {
|
|
949
|
+
this.id = id;
|
|
950
|
+
this.child = child;
|
|
951
|
+
this.shardList = shardList;
|
|
952
|
+
this.totalShards = totalShards;
|
|
953
|
+
this.status = "starting";
|
|
954
|
+
this.eventManager = new EventManager((message2) => {
|
|
955
|
+
return new Promise((resolve, reject) => {
|
|
956
|
+
this.child.send(message2, (error) => {
|
|
957
|
+
if (error) {
|
|
958
|
+
reject(error);
|
|
959
|
+
} else {
|
|
960
|
+
resolve();
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
});
|
|
964
|
+
}, (message2) => {
|
|
965
|
+
if (this._onMessage) {
|
|
966
|
+
this._onMessage(message2);
|
|
967
|
+
}
|
|
968
|
+
}, (message2) => {
|
|
969
|
+
if (this._onRequest) {
|
|
970
|
+
return this._onRequest(message2);
|
|
971
|
+
}
|
|
972
|
+
return void 0;
|
|
973
|
+
});
|
|
974
|
+
this.child.on("message", (message2) => {
|
|
975
|
+
this.eventManager.receive(message2);
|
|
976
|
+
});
|
|
977
|
+
this.child.on("exit", () => {
|
|
978
|
+
this.eventManager.close("child process exited");
|
|
979
|
+
});
|
|
980
|
+
this.child.on("error", () => {
|
|
981
|
+
this.eventManager.close("child process error");
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
onMessage(callback) {
|
|
985
|
+
this._onMessage = callback;
|
|
986
|
+
}
|
|
987
|
+
onRequest(callback) {
|
|
988
|
+
this._onRequest = callback;
|
|
989
|
+
}
|
|
990
|
+
sendMessage(data) {
|
|
991
|
+
this.eventManager.send({
|
|
992
|
+
type: "CUSTOM",
|
|
993
|
+
data
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
sendRequest(data, timeout = 5e3) {
|
|
997
|
+
return this.eventManager.request({
|
|
998
|
+
type: "CUSTOM",
|
|
999
|
+
data
|
|
1000
|
+
}, timeout);
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
// src/instance/BotInstance.ts
|
|
1005
|
+
import { fork } from "child_process";
|
|
1006
|
+
var BotInstance = class {
|
|
1007
|
+
entryPoint;
|
|
1008
|
+
execArgv;
|
|
1009
|
+
clients = /* @__PURE__ */ new Map();
|
|
1010
|
+
constructor(entryPoint, execArgv) {
|
|
1011
|
+
this.entryPoint = entryPoint;
|
|
1012
|
+
this.execArgv = execArgv ?? [];
|
|
1013
|
+
}
|
|
1014
|
+
eventMap = {
|
|
1015
|
+
"message": void 0,
|
|
1016
|
+
"request": void 0,
|
|
1017
|
+
"PROCESS_KILLED": void 0,
|
|
1018
|
+
"PROCESS_SPAWNED": void 0,
|
|
1019
|
+
"ERROR": void 0,
|
|
1020
|
+
"PROCESS_ERROR": void 0,
|
|
1021
|
+
"CLUSTER_READY": void 0,
|
|
1022
|
+
"CLUSTER_ERROR": void 0,
|
|
1023
|
+
"CLUSTER_RECLUSTER": void 0,
|
|
1024
|
+
"BRIDGE_CONNECTION_ESTABLISHED": void 0,
|
|
1025
|
+
"BRIDGE_CONNECTION_CLOSED": void 0,
|
|
1026
|
+
"BRIDGE_CONNECTION_STATUS_CHANGE": void 0,
|
|
1027
|
+
"INSTANCE_STOP": void 0,
|
|
1028
|
+
"INSTANCE_STOPPED": void 0,
|
|
1029
|
+
"SELF_CHECK_SUCCESS": void 0,
|
|
1030
|
+
"SELF_CHECK_ERROR": void 0,
|
|
1031
|
+
"SELF_CHECK_RECEIVED": void 0
|
|
1032
|
+
};
|
|
1033
|
+
startProcess(instanceID, clusterID, shardList, totalShards, token, intents) {
|
|
1034
|
+
try {
|
|
1035
|
+
const child = fork(this.entryPoint, {
|
|
1036
|
+
env: {
|
|
1037
|
+
INSTANCE_ID: instanceID.toString(),
|
|
1038
|
+
CLUSTER_ID: clusterID.toString(),
|
|
1039
|
+
SHARD_LIST: shardList.join(","),
|
|
1040
|
+
TOTAL_SHARDS: totalShards.toString(),
|
|
1041
|
+
TOKEN: token,
|
|
1042
|
+
INTENTS: intents.join(","),
|
|
1043
|
+
FORCE_COLOR: "true"
|
|
1044
|
+
},
|
|
1045
|
+
stdio: "inherit",
|
|
1046
|
+
execArgv: this.execArgv,
|
|
1047
|
+
silent: false
|
|
1048
|
+
});
|
|
1049
|
+
const client = new ClusterProcess(clusterID, child, shardList, totalShards);
|
|
1050
|
+
child.stdout?.on("data", (data) => {
|
|
1051
|
+
process.stdout.write(data);
|
|
1052
|
+
});
|
|
1053
|
+
child.stderr?.on("data", (data) => {
|
|
1054
|
+
process.stderr.write(data);
|
|
1055
|
+
});
|
|
1056
|
+
child.on("spawn", () => {
|
|
1057
|
+
if (this.eventMap.PROCESS_SPAWNED) this.eventMap.PROCESS_SPAWNED(client);
|
|
1058
|
+
this.setClusterSpawned(client);
|
|
1059
|
+
this.clients.set(clusterID, client);
|
|
1060
|
+
client.onMessage((message2) => {
|
|
1061
|
+
this.onMessage(client, message2);
|
|
1062
|
+
});
|
|
1063
|
+
client.onRequest((message2) => {
|
|
1064
|
+
return this.onRequest(client, message2);
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
child.on("error", (err) => {
|
|
1068
|
+
if (this.eventMap.PROCESS_ERROR) this.eventMap.PROCESS_ERROR(client, err);
|
|
1069
|
+
});
|
|
1070
|
+
child.on("exit", (err) => {
|
|
1071
|
+
if (client.status !== "stopped") {
|
|
1072
|
+
client.status = "stopped";
|
|
1073
|
+
this.killProcess(client, `Process exited: ${err?.message}`);
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
throw new Error(`Failed to start process for cluster ${clusterID}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
killProcess(client, reason) {
|
|
1081
|
+
client.status = "stopped";
|
|
1082
|
+
if (client.child && client.child.pid) {
|
|
1083
|
+
if (client.child.kill("SIGKILL")) {
|
|
1084
|
+
if (this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, true);
|
|
1085
|
+
} else {
|
|
1086
|
+
if (this.eventMap.ERROR) this.eventMap.ERROR(`Failed to kill process for cluster ${client.id}`);
|
|
1087
|
+
client.child.kill("SIGKILL");
|
|
1088
|
+
}
|
|
1089
|
+
} else {
|
|
1090
|
+
if (this.eventMap.PROCESS_KILLED) this.eventMap.PROCESS_KILLED(client, reason, false);
|
|
1091
|
+
}
|
|
1092
|
+
this.clients.delete(client.id);
|
|
1093
|
+
this.setClusterStopped(client, reason);
|
|
1094
|
+
}
|
|
1095
|
+
onMessage(client, message2) {
|
|
1096
|
+
if (message2.type === "CLUSTER_READY") {
|
|
1097
|
+
client.status = "running";
|
|
1098
|
+
if (this.eventMap.CLUSTER_READY) this.eventMap.CLUSTER_READY(client);
|
|
1099
|
+
this.setClusterReady(client, message2.guilds || 0, message2.members || 0);
|
|
1100
|
+
}
|
|
1101
|
+
if (message2.type === "CLUSTER_ERROR") {
|
|
1102
|
+
client.status = "stopped";
|
|
1103
|
+
if (this.eventMap.CLUSTER_ERROR) this.eventMap.CLUSTER_ERROR(client, message2.error);
|
|
1104
|
+
this.killProcess(client, "Cluster error: " + message2.error);
|
|
1105
|
+
}
|
|
1106
|
+
if (message2.type == "CUSTOM" && this.eventMap.message) {
|
|
1107
|
+
this.eventMap.message(client, message2.data);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
on(event, listener) {
|
|
1111
|
+
this.eventMap[event] = listener;
|
|
1112
|
+
}
|
|
1113
|
+
sendRequestToClusterOfGuild(guildID, message2, timeout = 5e3) {
|
|
1114
|
+
return new Promise((resolve, reject) => {
|
|
1115
|
+
for (const client of this.clients.values()) {
|
|
1116
|
+
const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);
|
|
1117
|
+
if (client.shardList.includes(shardID)) {
|
|
1118
|
+
client.eventManager.request({
|
|
1119
|
+
type: "CUSTOM",
|
|
1120
|
+
data: message2
|
|
1121
|
+
}, timeout).then(resolve).catch(reject);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
reject(new Error(`No cluster found for guild ${guildID}`));
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
sendRequestToCluster(cluster, message2, timeout = 5e3) {
|
|
1129
|
+
return new Promise((resolve, reject) => {
|
|
1130
|
+
cluster.eventManager.request({
|
|
1131
|
+
type: "CUSTOM",
|
|
1132
|
+
data: message2
|
|
1133
|
+
}, timeout).then(resolve).catch(reject);
|
|
1134
|
+
return;
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
// src/instance/ManagedInstance.ts
|
|
1140
|
+
import { Client } from "net-ipc";
|
|
1141
|
+
var BridgeConnectionStatus = /* @__PURE__ */ ((BridgeConnectionStatus2) => {
|
|
1142
|
+
BridgeConnectionStatus2[BridgeConnectionStatus2["CONNECTED"] = 0] = "CONNECTED";
|
|
1143
|
+
BridgeConnectionStatus2[BridgeConnectionStatus2["DISCONNECTED"] = 1] = "DISCONNECTED";
|
|
1144
|
+
return BridgeConnectionStatus2;
|
|
1145
|
+
})(BridgeConnectionStatus || {});
|
|
1146
|
+
var ManagedInstance = class extends BotInstance {
|
|
1147
|
+
host;
|
|
1148
|
+
port;
|
|
1149
|
+
instanceID;
|
|
1150
|
+
eventManager;
|
|
1151
|
+
connectionStatus = 1 /* DISCONNECTED */;
|
|
1152
|
+
data;
|
|
1153
|
+
dev = false;
|
|
1154
|
+
constructor(entryPoint, host, port, instanceID, data, execArgv, dev) {
|
|
1155
|
+
super(entryPoint, execArgv);
|
|
1156
|
+
this.host = host;
|
|
1157
|
+
this.port = port;
|
|
1158
|
+
this.instanceID = instanceID;
|
|
1159
|
+
this.data = data;
|
|
1160
|
+
this.dev = dev || false;
|
|
1161
|
+
}
|
|
1162
|
+
start() {
|
|
1163
|
+
const client = new Client({
|
|
1164
|
+
host: this.host,
|
|
1165
|
+
port: this.port,
|
|
1166
|
+
reconnect: true,
|
|
1167
|
+
retries: 100
|
|
1168
|
+
});
|
|
1169
|
+
this.eventManager = new EventManager((message2) => {
|
|
1170
|
+
if (client.status == 3) {
|
|
1171
|
+
return client.send(message2);
|
|
1172
|
+
}
|
|
1173
|
+
return Promise.reject(new Error("Client is not ready to send messages"));
|
|
1174
|
+
}, (message2) => {
|
|
1175
|
+
const m2 = message2;
|
|
1176
|
+
if (m2.type == "CLUSTER_CREATE") {
|
|
1177
|
+
this.onClusterCreate(m2.data);
|
|
1178
|
+
} else if (m2.type == "CLUSTER_STOP") {
|
|
1179
|
+
this.onClusterStop(m2.data);
|
|
1180
|
+
} else if (m2.type == "CLUSTER_RECLUSTER") {
|
|
1181
|
+
this.onClusterRecluster(m2.data);
|
|
1182
|
+
} else if (m2.type == "INSTANCE_STOP") {
|
|
1183
|
+
if (this.eventMap.INSTANCE_STOP) {
|
|
1184
|
+
this.eventMap.INSTANCE_STOP();
|
|
1185
|
+
}
|
|
1186
|
+
} else if (m2.type == "INSTANCE_STOPPED") {
|
|
1187
|
+
if (this.eventMap.INSTANCE_STOPPED) {
|
|
1188
|
+
this.eventMap.INSTANCE_STOPPED();
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}, (message2) => {
|
|
1192
|
+
return this.onBridgeRequest(message2);
|
|
1193
|
+
});
|
|
1194
|
+
setInterval(() => {
|
|
1195
|
+
if (this.connectionStatus == 0 /* CONNECTED */) {
|
|
1196
|
+
this.selfCheck();
|
|
1197
|
+
}
|
|
1198
|
+
}, 2500);
|
|
1199
|
+
client.connect({
|
|
1200
|
+
id: this.instanceID,
|
|
1201
|
+
dev: this.dev,
|
|
1202
|
+
data: this.data
|
|
1203
|
+
}).then((_) => {
|
|
1204
|
+
if (this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED();
|
|
1205
|
+
this.connectionStatus = 0 /* CONNECTED */;
|
|
1206
|
+
client.on("message", (message2) => {
|
|
1207
|
+
this.eventManager?.receive(message2);
|
|
1208
|
+
});
|
|
1209
|
+
client.on("close", (reason) => {
|
|
1210
|
+
if (this.eventMap.BRIDGE_CONNECTION_CLOSED) this.eventMap.BRIDGE_CONNECTION_CLOSED(reason);
|
|
1211
|
+
if (this.connectionStatus == 0 /* CONNECTED */) {
|
|
1212
|
+
this.clients.forEach((client2) => {
|
|
1213
|
+
this.killProcess(client2, "Bridge connection closed");
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
this.connectionStatus = 1 /* DISCONNECTED */;
|
|
1217
|
+
});
|
|
1218
|
+
client.on("status", (status) => {
|
|
1219
|
+
if (this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE) this.eventMap.BRIDGE_CONNECTION_STATUS_CHANGE(status);
|
|
1220
|
+
if (status == 4) {
|
|
1221
|
+
if (this.connectionStatus == 0 /* CONNECTED */) {
|
|
1222
|
+
this.clients.forEach((client2) => {
|
|
1223
|
+
this.killProcess(client2, "Bridge connection closed");
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
this.connectionStatus = 1 /* DISCONNECTED */;
|
|
1227
|
+
} else if (status == 3) {
|
|
1228
|
+
this.connectionStatus = 0 /* CONNECTED */;
|
|
1229
|
+
if (this.eventMap.BRIDGE_CONNECTION_ESTABLISHED) this.eventMap.BRIDGE_CONNECTION_ESTABLISHED();
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
selfCheck() {
|
|
1235
|
+
this.eventManager.request({
|
|
1236
|
+
type: "SELF_CHECK"
|
|
1237
|
+
}, 1e3 * 60).then((r) => {
|
|
1238
|
+
const response = r;
|
|
1239
|
+
if (this.eventMap.SELF_CHECK_RECEIVED) {
|
|
1240
|
+
this.eventMap.SELF_CHECK_RECEIVED(response);
|
|
1241
|
+
}
|
|
1242
|
+
const startingClusters = this.clients.values().filter((c) => c.status == "starting").toArray();
|
|
1243
|
+
startingClusters.forEach((c) => {
|
|
1244
|
+
if (Date.now() - c.createdAt > 10 * 60 * 1e3) {
|
|
1245
|
+
this.killProcess(c, "Cluster took too long to start");
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
const wrongClusters = this.clients.values().filter((c) => !response.clusterList.includes(c.id)).toArray();
|
|
1249
|
+
if (wrongClusters.length > 0) {
|
|
1250
|
+
if (this.eventMap.SELF_CHECK_ERROR) {
|
|
1251
|
+
this.eventMap.SELF_CHECK_ERROR(`Self check found wrong clusters: ${wrongClusters.map((c) => c.id).join(", ")}`);
|
|
1252
|
+
}
|
|
1253
|
+
wrongClusters.forEach((c) => {
|
|
1254
|
+
this.killProcess(c, "Self check found wrong cluster");
|
|
1255
|
+
});
|
|
1256
|
+
} else {
|
|
1257
|
+
if (this.eventMap.SELF_CHECK_SUCCESS) {
|
|
1258
|
+
this.eventMap.SELF_CHECK_SUCCESS();
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}).catch((err) => {
|
|
1262
|
+
if (this.eventMap.SELF_CHECK_ERROR) {
|
|
1263
|
+
this.eventMap.SELF_CHECK_ERROR(`Self check failed: ${err}`);
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
setClusterStopped(client, reason) {
|
|
1268
|
+
this.eventManager?.send({
|
|
1269
|
+
type: "CLUSTER_STOPPED",
|
|
1270
|
+
data: {
|
|
1271
|
+
id: client.id,
|
|
1272
|
+
reason
|
|
1273
|
+
}
|
|
1274
|
+
}).catch(() => {
|
|
1275
|
+
return null;
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
setClusterReady(client, guilds, members) {
|
|
1279
|
+
this.eventManager?.send({
|
|
1280
|
+
type: "CLUSTER_READY",
|
|
1281
|
+
data: {
|
|
1282
|
+
id: client.id,
|
|
1283
|
+
guilds,
|
|
1284
|
+
members
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
setClusterSpawned(client) {
|
|
1289
|
+
this.eventManager?.send({
|
|
1290
|
+
type: "CLUSTER_SPAWNED",
|
|
1291
|
+
data: {
|
|
1292
|
+
id: client.id
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
onClusterCreate(message2) {
|
|
1297
|
+
const m2 = message2;
|
|
1298
|
+
if (this.clients.has(m2.clusterID)) {
|
|
1299
|
+
this.eventManager?.send({
|
|
1300
|
+
type: "CLUSTER_STOPPED",
|
|
1301
|
+
data: {
|
|
1302
|
+
id: m2.clusterID,
|
|
1303
|
+
reason: "Cluster already exists"
|
|
1304
|
+
}
|
|
1305
|
+
}).catch(() => {
|
|
1306
|
+
return null;
|
|
1307
|
+
});
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
this.startProcess(this.instanceID, m2.clusterID, m2.shardList, m2.totalShards, m2.token, m2.intents);
|
|
1311
|
+
}
|
|
1312
|
+
onClusterStop(message2) {
|
|
1313
|
+
const m2 = message2;
|
|
1314
|
+
const cluster = this.clients.get(m2.id);
|
|
1315
|
+
if (cluster) {
|
|
1316
|
+
this.killProcess(cluster, `Request to stop cluster ${m2.id}`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
onClusterRecluster(message2) {
|
|
1320
|
+
const m2 = message2;
|
|
1321
|
+
const cluster = this.clients.get(m2.clusterID);
|
|
1322
|
+
if (this.eventMap.CLUSTER_RECLUSTER && cluster) {
|
|
1323
|
+
this.eventMap.CLUSTER_RECLUSTER(cluster);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
onRequest(client, message2) {
|
|
1327
|
+
if (message2.type === "REDIRECT_REQUEST_TO_GUILD") {
|
|
1328
|
+
const guildID = message2.guildID;
|
|
1329
|
+
const data = message2.data;
|
|
1330
|
+
const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);
|
|
1331
|
+
if (client.shardList.includes(shardID)) {
|
|
1332
|
+
return client.eventManager.request({
|
|
1333
|
+
type: "CUSTOM",
|
|
1334
|
+
data
|
|
1335
|
+
}, 5e3);
|
|
1336
|
+
} else {
|
|
1337
|
+
return this.eventManager.request({
|
|
1338
|
+
type: "REDIRECT_REQUEST_TO_GUILD",
|
|
1339
|
+
guildID,
|
|
1340
|
+
data
|
|
1341
|
+
}, 5e3);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
if (message2.type == "BROADCAST_EVAL") {
|
|
1345
|
+
return this.eventManager.request({
|
|
1346
|
+
type: "BROADCAST_EVAL",
|
|
1347
|
+
data: message2.data
|
|
1348
|
+
}, 5e3);
|
|
1349
|
+
}
|
|
1350
|
+
if (message2.type == "CUSTOM" && this.eventMap.request) {
|
|
1351
|
+
return new Promise((resolve, reject) => {
|
|
1352
|
+
this.eventMap.request(client, message2.data, resolve, reject);
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
return Promise.reject(new Error(`Unknown request type: ${message2.type}`));
|
|
1356
|
+
}
|
|
1357
|
+
onBridgeRequest(message2) {
|
|
1358
|
+
if (message2.type === "REDIRECT_REQUEST_TO_GUILD") {
|
|
1359
|
+
const clusterID = message2.clusterID;
|
|
1360
|
+
const data = message2.data;
|
|
1361
|
+
const cluster = this.clients.get(clusterID);
|
|
1362
|
+
if (cluster) {
|
|
1363
|
+
return cluster.eventManager.request({
|
|
1364
|
+
type: "CUSTOM",
|
|
1365
|
+
data
|
|
1366
|
+
}, 5e3);
|
|
1367
|
+
} else {
|
|
1368
|
+
return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`));
|
|
1369
|
+
}
|
|
1370
|
+
} else if (message2.type == "CLUSTER_HEARTBEAT") {
|
|
1371
|
+
const clusterID = message2.data.clusterID;
|
|
1372
|
+
const cluster = this.clients.get(clusterID);
|
|
1373
|
+
if (cluster) {
|
|
1374
|
+
return new Promise((resolve, reject) => {
|
|
1375
|
+
cluster.eventManager.request({
|
|
1376
|
+
type: "CLUSTER_HEARTBEAT"
|
|
1377
|
+
}, 15e3).then((r) => {
|
|
1378
|
+
resolve(r);
|
|
1379
|
+
}).catch((err) => {
|
|
1380
|
+
reject(err);
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
} else {
|
|
1384
|
+
return Promise.reject(new Error(`Cluster is not here. Cluster ID: ${clusterID}`));
|
|
1385
|
+
}
|
|
1386
|
+
} else if (message2.type == "BROADCAST_EVAL") {
|
|
1387
|
+
return Promise.all(this.clients.values().filter((c) => c.status == "running").map((c) => {
|
|
1388
|
+
return c.eventManager.request({
|
|
1389
|
+
type: "BROADCAST_EVAL",
|
|
1390
|
+
data: message2.data
|
|
1391
|
+
}, 5e3);
|
|
1392
|
+
}));
|
|
1393
|
+
}
|
|
1394
|
+
return Promise.reject(new Error(`Unknown request type: ${message2.type}`));
|
|
1395
|
+
}
|
|
1396
|
+
stopInstance() {
|
|
1397
|
+
this.eventManager?.send({
|
|
1398
|
+
type: "INSTANCE_STOP"
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
|
|
1403
|
+
// src/instance/StandaloneInstance.ts
|
|
1404
|
+
var StandaloneInstance = class extends BotInstance {
|
|
1405
|
+
totalClusters;
|
|
1406
|
+
shardsPerCluster;
|
|
1407
|
+
token;
|
|
1408
|
+
intents;
|
|
1409
|
+
constructor(entryPoint, shardsPerCluster, totalClusters, token, intents, execArgv) {
|
|
1410
|
+
super(entryPoint, execArgv);
|
|
1411
|
+
this.shardsPerCluster = shardsPerCluster;
|
|
1412
|
+
this.totalClusters = totalClusters;
|
|
1413
|
+
this.token = token;
|
|
1414
|
+
this.intents = intents;
|
|
1415
|
+
}
|
|
1416
|
+
get totalShards() {
|
|
1417
|
+
return this.shardsPerCluster * this.totalClusters;
|
|
1418
|
+
}
|
|
1419
|
+
calculateClusters() {
|
|
1420
|
+
const clusters = {};
|
|
1421
|
+
for (let i = 0; i < this.totalClusters; i++) {
|
|
1422
|
+
clusters[i] = [];
|
|
1423
|
+
for (let j = 0; j < this.shardsPerCluster; j++) {
|
|
1424
|
+
clusters[i].push(i * this.shardsPerCluster + j);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
return clusters;
|
|
1428
|
+
}
|
|
1429
|
+
start() {
|
|
1430
|
+
const clusters = this.calculateClusters();
|
|
1431
|
+
for (const [id, shardList] of Object.entries(clusters)) {
|
|
1432
|
+
this.startProcess(1, Number(id), shardList, this.totalShards, this.token, this.intents);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
setClusterStopped(client, reason) {
|
|
1436
|
+
this.clients.delete(client.id);
|
|
1437
|
+
this.restartProcess(client);
|
|
1438
|
+
}
|
|
1439
|
+
setClusterReady(client) {
|
|
1440
|
+
}
|
|
1441
|
+
setClusterSpawned(client) {
|
|
1442
|
+
}
|
|
1443
|
+
restartProcess(client) {
|
|
1444
|
+
this.startProcess(1, client.id, client.shardList, this.totalShards, this.token, this.intents);
|
|
1445
|
+
}
|
|
1446
|
+
onRequest(client, message2) {
|
|
1447
|
+
if (message2.type === "REDIRECT_REQUEST_TO_GUILD") {
|
|
1448
|
+
const guildID = message2.guildID;
|
|
1449
|
+
const data = message2.data;
|
|
1450
|
+
const shardID = ShardingUtil.getShardIDForGuild(guildID, client.totalShards);
|
|
1451
|
+
if (client.shardList.includes(shardID)) {
|
|
1452
|
+
return client.eventManager.request({
|
|
1453
|
+
type: "CUSTOM",
|
|
1454
|
+
data
|
|
1455
|
+
}, 5e3);
|
|
1456
|
+
} else {
|
|
1457
|
+
return Promise.reject(new Error(`Shard ID ${shardID} not found in cluster ${client.id} for guild ${guildID}`));
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
if (message2.type == "BROADCAST_EVAL") {
|
|
1461
|
+
return Promise.all(
|
|
1462
|
+
this.clients.values().map((c) => {
|
|
1463
|
+
return c.eventManager.request({
|
|
1464
|
+
type: "BROADCAST_EVAL",
|
|
1465
|
+
data: message2.data
|
|
1466
|
+
}, 5e3);
|
|
1467
|
+
})
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
if (message2.type == "CUSTOM" && this.eventMap.request) {
|
|
1471
|
+
return new Promise((resolve, reject) => {
|
|
1472
|
+
this.eventMap.request(client, message2.data, resolve, reject);
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
return Promise.reject(new Error(`Unknown request type: ${message2.type}`));
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
export {
|
|
1479
|
+
BotInstance,
|
|
1480
|
+
Bridge,
|
|
1481
|
+
BridgeClientCluster,
|
|
1482
|
+
BridgeClientClusterConnectionStatus,
|
|
1483
|
+
BridgeClientConnection,
|
|
1484
|
+
BridgeClientConnectionStatus,
|
|
1485
|
+
BridgeConnectionStatus,
|
|
1486
|
+
Cluster,
|
|
1487
|
+
ClusterCalculator,
|
|
1488
|
+
ClusterProcess,
|
|
1489
|
+
EventManager,
|
|
1490
|
+
ManagedInstance,
|
|
1491
|
+
ShardingUtil,
|
|
1492
|
+
StandaloneInstance
|
|
1493
|
+
};
|
|
1494
|
+
//# sourceMappingURL=index.mjs.map
|