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/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