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