lunibee 0.1.6 → 0.1.7
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/README.md +3 -3
- package/dist/builders/commands.d.ts +37 -0
- package/dist/builders/components.d.ts +19 -12
- package/dist/builders/embed.d.ts +3 -1
- package/dist/builders/index.d.ts +1 -1
- package/dist/builders/index.js +146 -19
- package/dist/builders/index.js.map +8 -7
- package/dist/collection/index.d.ts +30 -0
- package/dist/collection/index.js +78 -5
- package/dist/collection/index.js.map +4 -4
- package/dist/core/events.d.ts +2 -0
- package/dist/core/index.d.ts +38 -0
- package/dist/core/index.js +754 -175
- package/dist/core/index.js.map +24 -22
- package/dist/core/permissions.d.ts +112 -53
- package/dist/formatters/index.d.ts +43 -3
- package/dist/formatters/index.js +64 -1
- package/dist/formatters/index.js.map +3 -3
- package/dist/handlers/index.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1762 -291
- package/dist/index.js.map +43 -38
- package/dist/managers/emoji.d.ts +38 -0
- package/dist/managers/guild.d.ts +112 -1
- package/dist/managers/index.d.ts +31 -1
- package/dist/managers/index.js +803 -77
- package/dist/managers/index.js.map +21 -20
- package/dist/rest/index.d.ts +101 -11
- package/dist/rest/index.js +301 -45
- package/dist/rest/index.js.map +8 -6
- package/dist/rest/redis.d.ts +37 -0
- package/dist/rest/routes.d.ts +22 -0
- package/dist/rest/store.d.ts +38 -0
- package/dist/rest/webhook.d.ts +2 -2
- package/dist/sharding/cluster.d.ts +61 -0
- package/dist/sharding/index.d.ts +8 -0
- package/dist/sharding/index.js +286 -18
- package/dist/sharding/index.js.map +8 -7
- package/dist/structures/audit-log.d.ts +26 -0
- package/dist/structures/base.d.ts +32 -0
- package/dist/structures/index.d.ts +29 -1
- package/dist/structures/index.js +1022 -86
- package/dist/structures/index.js.map +14 -10
- package/dist/structures/resources.d.ts +192 -4
- package/dist/types/index.d.ts +76 -0
- package/dist/types/index.js.map +2 -2
- package/dist/utils/index.js.map +2 -2
- package/dist/voice/index.d.ts +88 -0
- package/dist/voice/index.js +204 -5
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/index.d.ts +40 -1
- package/dist/ws/index.js +102 -18
- package/dist/ws/index.js.map +4 -4
- package/package.json +16 -16
- package/dist/structures/permissions.d.ts +0 -73
package/dist/sharding/index.js
CHANGED
|
@@ -88,6 +88,22 @@ var GatewayOpcodes = {
|
|
|
88
88
|
Hello: 10,
|
|
89
89
|
HeartbeatAck: 11
|
|
90
90
|
};
|
|
91
|
+
var GatewayCloseCodes = {
|
|
92
|
+
UnknownError: 4000,
|
|
93
|
+
UnknownOpcode: 4001,
|
|
94
|
+
DecodeError: 4002,
|
|
95
|
+
NotAuthenticated: 4003,
|
|
96
|
+
AuthenticationFailed: 4004,
|
|
97
|
+
AlreadyAuthenticated: 4005,
|
|
98
|
+
InvalidSeq: 4007,
|
|
99
|
+
RateLimited: 4008,
|
|
100
|
+
SessionTimedOut: 4009,
|
|
101
|
+
InvalidShard: 4010,
|
|
102
|
+
ShardingRequired: 4011,
|
|
103
|
+
InvalidAPIVersion: 4012,
|
|
104
|
+
InvalidIntents: 4013,
|
|
105
|
+
DisallowedIntents: 4014
|
|
106
|
+
};
|
|
91
107
|
class GatewayError extends Error {
|
|
92
108
|
code;
|
|
93
109
|
constructor(message, code, options) {
|
|
@@ -121,6 +137,8 @@ class Gateway {
|
|
|
121
137
|
#rejectConnect;
|
|
122
138
|
#listeners = new Map;
|
|
123
139
|
#sendTimestamps = [];
|
|
140
|
+
ping = -1;
|
|
141
|
+
#decompressor;
|
|
124
142
|
constructor(options) {
|
|
125
143
|
if (!options.token?.trim())
|
|
126
144
|
throw new TypeError("A Gateway token is required.");
|
|
@@ -141,7 +159,12 @@ class Gateway {
|
|
|
141
159
|
browser: "Discord Android",
|
|
142
160
|
device: "Discord Android"
|
|
143
161
|
},
|
|
144
|
-
presence: {
|
|
162
|
+
presence: {
|
|
163
|
+
status: "online",
|
|
164
|
+
activities: [],
|
|
165
|
+
afk: false,
|
|
166
|
+
since: null
|
|
167
|
+
},
|
|
145
168
|
...options
|
|
146
169
|
};
|
|
147
170
|
if (this.#options.shardId < 0 || this.#options.shardId >= this.#options.shardCount)
|
|
@@ -156,10 +179,13 @@ class Gateway {
|
|
|
156
179
|
return this.#connectPromise;
|
|
157
180
|
this.#closed = false;
|
|
158
181
|
this.#setState("CONNECT" /* Connect */);
|
|
182
|
+
const connectURL = this.#options.compress ? url.includes("compress=") ? url : `${url}&compress=zlib-stream` : url;
|
|
183
|
+
if (this.#options.compress)
|
|
184
|
+
this.#initDecompressor();
|
|
159
185
|
this.#connectPromise = new Promise((resolve, reject) => {
|
|
160
186
|
this.#resolveConnect = resolve;
|
|
161
187
|
this.#rejectConnect = reject;
|
|
162
|
-
this.#open(
|
|
188
|
+
this.#open(connectURL);
|
|
163
189
|
});
|
|
164
190
|
return this.#connectPromise;
|
|
165
191
|
}
|
|
@@ -195,12 +221,15 @@ class Gateway {
|
|
|
195
221
|
send(payload) {
|
|
196
222
|
if (!payload || !Number.isInteger(payload.op))
|
|
197
223
|
throw new TypeError("Gateway payload must contain an integer opcode.");
|
|
224
|
+
return this.#dispatch(payload, false);
|
|
225
|
+
}
|
|
226
|
+
#dispatch(payload, privileged) {
|
|
198
227
|
if (this.#ws?.readyState !== WebSocket.OPEN)
|
|
199
228
|
return false;
|
|
200
229
|
const now = Date.now();
|
|
201
230
|
while (this.#sendTimestamps.length && now - this.#sendTimestamps[0] >= 60000)
|
|
202
231
|
this.#sendTimestamps.shift();
|
|
203
|
-
if (this.#sendTimestamps.length >= 115) {
|
|
232
|
+
if (!privileged && this.#sendTimestamps.length >= 115) {
|
|
204
233
|
this.#emitError(new GatewayError("Gateway send rate budget exhausted."));
|
|
205
234
|
return false;
|
|
206
235
|
}
|
|
@@ -271,7 +300,11 @@ class Gateway {
|
|
|
271
300
|
ws.addEventListener("message", (event) => {
|
|
272
301
|
this.#lastMessageAt = Date.now();
|
|
273
302
|
this.#zombieReported = false;
|
|
274
|
-
this.#
|
|
303
|
+
if (this.#options.compress && event.data instanceof ArrayBuffer) {
|
|
304
|
+
this.#decompress(new Uint8Array(event.data)).then((text) => this.#message(ws, text)).catch((err) => this.#emitError(err));
|
|
305
|
+
} else {
|
|
306
|
+
this.#message(ws, String(event.data));
|
|
307
|
+
}
|
|
275
308
|
});
|
|
276
309
|
ws.addEventListener("close", (event) => this.#close(ws, event.code));
|
|
277
310
|
ws.addEventListener("error", () => this.#emitError(new GatewayError("Gateway WebSocket error")));
|
|
@@ -279,7 +312,7 @@ class Gateway {
|
|
|
279
312
|
#startZombieDetection() {
|
|
280
313
|
if (this.#zombieTimer)
|
|
281
314
|
clearInterval(this.#zombieTimer);
|
|
282
|
-
const interval = Math.max(1, Math.min(
|
|
315
|
+
const interval = Math.max(1, Math.min(1000, this.#options.heartbeatAckTimeout, this.#options.zombieTimeout / 2));
|
|
283
316
|
this.#zombieTimer = setInterval(() => {
|
|
284
317
|
if (this.#closed || this.#ws?.readyState !== WebSocket.OPEN || this.#lastMessageAt === 0)
|
|
285
318
|
return;
|
|
@@ -330,18 +363,24 @@ class Gateway {
|
|
|
330
363
|
break;
|
|
331
364
|
case GatewayOpcodes.HeartbeatAck:
|
|
332
365
|
this.#heartbeatACK = true;
|
|
366
|
+
this.ping = Date.now() - this.#heartbeatSentAt;
|
|
333
367
|
this.#clearHeartbeatAckTimer();
|
|
334
368
|
this.#emit("heartbeatAck", payload.d);
|
|
335
369
|
break;
|
|
336
370
|
case GatewayOpcodes.Reconnect:
|
|
337
371
|
ws.close(1001, "Server requested reconnect");
|
|
338
372
|
break;
|
|
339
|
-
case GatewayOpcodes.InvalidSession:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
373
|
+
case GatewayOpcodes.InvalidSession: {
|
|
374
|
+
const resumable = payload.d === true;
|
|
375
|
+
if (!resumable) {
|
|
376
|
+
this.#sessionId = undefined;
|
|
377
|
+
this.#sequence = null;
|
|
378
|
+
this.#resumeURL = undefined;
|
|
379
|
+
}
|
|
380
|
+
this.#emit("invalidSession", resumable);
|
|
381
|
+
ws.close(resumable ? GatewayCloseCodes.UnknownError : 1000, resumable ? "Invalid session (resumable)" : "Invalid session");
|
|
344
382
|
break;
|
|
383
|
+
}
|
|
345
384
|
}
|
|
346
385
|
}
|
|
347
386
|
#handleDispatch(event, data) {
|
|
@@ -356,6 +395,10 @@ class Gateway {
|
|
|
356
395
|
this.#attempt = 0;
|
|
357
396
|
this.#setState("READY" /* Ready */);
|
|
358
397
|
this.#emit("ready", data);
|
|
398
|
+
} else if (event === "RESUMED") {
|
|
399
|
+
this.#attempt = 0;
|
|
400
|
+
this.#setState("READY" /* Ready */);
|
|
401
|
+
this.#emit("resumed", data);
|
|
359
402
|
}
|
|
360
403
|
this.#emit("RAW", { event: event ?? "UNKNOWN", data });
|
|
361
404
|
if (this.#closed)
|
|
@@ -433,12 +476,12 @@ class Gateway {
|
|
|
433
476
|
#sendHeartbeat() {
|
|
434
477
|
this.#heartbeatACK = false;
|
|
435
478
|
this.#heartbeatSentAt = Date.now();
|
|
436
|
-
if (!this
|
|
479
|
+
if (!this.#dispatch({
|
|
437
480
|
op: GatewayOpcodes.Heartbeat,
|
|
438
481
|
d: this.#sequence,
|
|
439
482
|
s: null,
|
|
440
483
|
t: null
|
|
441
|
-
})) {
|
|
484
|
+
}, true)) {
|
|
442
485
|
this.#emitError(new GatewayError("Unable to send Gateway heartbeat because the WebSocket is not open."));
|
|
443
486
|
return;
|
|
444
487
|
}
|
|
@@ -471,14 +514,23 @@ class Gateway {
|
|
|
471
514
|
if (action === "identify") {
|
|
472
515
|
this.#sequence = null;
|
|
473
516
|
this.#sessionId = undefined;
|
|
517
|
+
this.#resumeURL = undefined;
|
|
474
518
|
}
|
|
475
519
|
this.#setState("RECONNECT" /* Reconnect */);
|
|
476
520
|
this.#scheduleReconnect();
|
|
477
521
|
}
|
|
478
522
|
#closeAction(code) {
|
|
479
|
-
|
|
523
|
+
const fatal = [
|
|
524
|
+
GatewayCloseCodes.AuthenticationFailed,
|
|
525
|
+
GatewayCloseCodes.InvalidShard,
|
|
526
|
+
GatewayCloseCodes.ShardingRequired,
|
|
527
|
+
GatewayCloseCodes.InvalidAPIVersion,
|
|
528
|
+
GatewayCloseCodes.InvalidIntents,
|
|
529
|
+
GatewayCloseCodes.DisallowedIntents
|
|
530
|
+
];
|
|
531
|
+
if (fatal.includes(code))
|
|
480
532
|
return "stop";
|
|
481
|
-
if (
|
|
533
|
+
if (code === GatewayCloseCodes.InvalidSeq || code === GatewayCloseCodes.SessionTimedOut)
|
|
482
534
|
return "identify";
|
|
483
535
|
return this.#sessionId && this.#sequence !== null ? "resume" : "identify";
|
|
484
536
|
}
|
|
@@ -491,7 +543,7 @@ class Gateway {
|
|
|
491
543
|
return;
|
|
492
544
|
}
|
|
493
545
|
const delay = Math.min(this.#options.reconnectMaxDelay, this.#options.reconnectBaseDelay * 2 ** this.#attempt++);
|
|
494
|
-
const jitter = Math.random() * Math.
|
|
546
|
+
const jitter = Math.random() * Math.max(1, delay * 0.25);
|
|
495
547
|
this.#reconnectTimer = setTimeout(() => {
|
|
496
548
|
this.#reconnectTimer = undefined;
|
|
497
549
|
this.#open(this.#resumeURL ?? "wss://gateway.discord.gg/?v=10&encoding=json");
|
|
@@ -561,6 +613,37 @@ class Gateway {
|
|
|
561
613
|
this.state = next;
|
|
562
614
|
this.#emit("stateChange", { previous, next });
|
|
563
615
|
}
|
|
616
|
+
#initDecompressor() {
|
|
617
|
+
const ds = new DecompressionStream("deflate-raw");
|
|
618
|
+
this.#decompressor = {
|
|
619
|
+
writer: ds.writable.getWriter(),
|
|
620
|
+
reader: ds.readable.getReader()
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
async#decompress(chunk) {
|
|
624
|
+
if (!this.#decompressor)
|
|
625
|
+
this.#initDecompressor();
|
|
626
|
+
const { writer, reader } = this.#decompressor;
|
|
627
|
+
await writer.write(chunk);
|
|
628
|
+
const parts = [];
|
|
629
|
+
while (true) {
|
|
630
|
+
const { done, value } = await Promise.race([
|
|
631
|
+
reader.read(),
|
|
632
|
+
new Promise((resolve) => setTimeout(() => resolve({ done: true, value: undefined }), 50))
|
|
633
|
+
]);
|
|
634
|
+
if (done || !value)
|
|
635
|
+
break;
|
|
636
|
+
parts.push(value);
|
|
637
|
+
}
|
|
638
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
639
|
+
const merged = new Uint8Array(total);
|
|
640
|
+
let offset = 0;
|
|
641
|
+
for (const p of parts) {
|
|
642
|
+
merged.set(p, offset);
|
|
643
|
+
offset += p.length;
|
|
644
|
+
}
|
|
645
|
+
return new TextDecoder().decode(merged);
|
|
646
|
+
}
|
|
564
647
|
}
|
|
565
648
|
|
|
566
649
|
// packages/sharding/src/bus.ts
|
|
@@ -636,8 +719,155 @@ class ShardBus {
|
|
|
636
719
|
}
|
|
637
720
|
}
|
|
638
721
|
}
|
|
722
|
+
// packages/sharding/src/cluster.ts
|
|
723
|
+
import { fork } from "child_process";
|
|
724
|
+
import { cpus } from "os";
|
|
725
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
726
|
+
|
|
727
|
+
class ClusterManager {
|
|
728
|
+
clusters = new Map;
|
|
729
|
+
shardCount = 0;
|
|
730
|
+
#options;
|
|
731
|
+
#auto;
|
|
732
|
+
#autoScaleTimer;
|
|
733
|
+
#spawned = false;
|
|
734
|
+
constructor(options) {
|
|
735
|
+
if (!options.token?.trim())
|
|
736
|
+
throw new TypeError("A Discord bot token is required.");
|
|
737
|
+
if (!options.script?.trim())
|
|
738
|
+
throw new TypeError("A script path is required for clustering.");
|
|
739
|
+
if (options.autoScaleInterval !== undefined && (!Number.isInteger(options.autoScaleInterval) || options.autoScaleInterval < 1000)) {
|
|
740
|
+
throw new RangeError("autoScaleInterval must be an integer of at least 1000 milliseconds.");
|
|
741
|
+
}
|
|
742
|
+
this.#auto = options.shardCount === "auto";
|
|
743
|
+
this.#options = { ...options };
|
|
744
|
+
}
|
|
745
|
+
async fetchRecommendedShardCount() {
|
|
746
|
+
const response = await fetch("https://discord.com/api/v10/gateway/bot", {
|
|
747
|
+
headers: {
|
|
748
|
+
Authorization: `Bot ${this.#options.token}`,
|
|
749
|
+
"User-Agent": "Lunibee/0.1.0"
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
if (!response.ok) {
|
|
753
|
+
throw new Error(`Gateway discovery failed with status ${response.status}`);
|
|
754
|
+
}
|
|
755
|
+
const data = await response.json();
|
|
756
|
+
if (typeof data.shards !== "number" || !Number.isInteger(data.shards) || data.shards < 1) {
|
|
757
|
+
throw new Error("Gateway discovery returned an invalid shard count.");
|
|
758
|
+
}
|
|
759
|
+
return data.shards;
|
|
760
|
+
}
|
|
761
|
+
async spawn() {
|
|
762
|
+
if (this.#spawned)
|
|
763
|
+
return;
|
|
764
|
+
if (typeof process?.versions?.bun === "string") {
|
|
765
|
+
throw new Error("ClusterManager requires the Node.js runtime; Bun does not support child_process.fork() for this clustering mode.");
|
|
766
|
+
}
|
|
767
|
+
this.#spawned = true;
|
|
768
|
+
const count = this.#options.shardCount === "auto" || this.#options.shardCount === undefined ? await this.fetchRecommendedShardCount() : this.#options.shardCount;
|
|
769
|
+
this.shardCount = count;
|
|
770
|
+
const clusterCount = this.#options.clusterCount ?? cpus().length;
|
|
771
|
+
const chunks = Array.from({ length: clusterCount }, () => []);
|
|
772
|
+
for (let i = 0;i < count; i++) {
|
|
773
|
+
chunks[i % clusterCount].push(i);
|
|
774
|
+
}
|
|
775
|
+
for (let i = 0;i < clusterCount; i++) {
|
|
776
|
+
if (chunks[i].length === 0)
|
|
777
|
+
continue;
|
|
778
|
+
const child = fork(this.#options.script, [], {
|
|
779
|
+
env: {
|
|
780
|
+
...process.env,
|
|
781
|
+
SHARD_LIST: chunks[i].join(","),
|
|
782
|
+
SHARD_COUNT: count.toString(),
|
|
783
|
+
CLUSTER_ID: i.toString()
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
this.clusters.set(i, {
|
|
787
|
+
id: i,
|
|
788
|
+
process: child,
|
|
789
|
+
shards: chunks[i]
|
|
790
|
+
});
|
|
791
|
+
await sleep(500);
|
|
792
|
+
}
|
|
793
|
+
if (this.#options.autoScaleInterval && this.#options.autoScaleInterval > 0) {
|
|
794
|
+
this.#autoScaleTimer = setInterval(() => {
|
|
795
|
+
this.checkAutoScale();
|
|
796
|
+
}, this.#options.autoScaleInterval);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
async checkAutoScale() {
|
|
800
|
+
if (!this.#auto)
|
|
801
|
+
return;
|
|
802
|
+
try {
|
|
803
|
+
const recommended = await this.fetchRecommendedShardCount();
|
|
804
|
+
if (recommended !== this.shardCount) {
|
|
805
|
+
await this.respawn(recommended);
|
|
806
|
+
}
|
|
807
|
+
} catch (error) {
|
|
808
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
809
|
+
this.#options.onAutoScaleError?.(normalized);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
async respawn(newShardCount) {
|
|
813
|
+
this.#options.shardCount = newShardCount;
|
|
814
|
+
await this.shutdownAll();
|
|
815
|
+
this.#spawned = false;
|
|
816
|
+
await this.spawn();
|
|
817
|
+
}
|
|
818
|
+
async shutdownAll(timeoutMs = this.#options.shutdownTimeout ?? 5000) {
|
|
819
|
+
if (this.#autoScaleTimer) {
|
|
820
|
+
clearInterval(this.#autoScaleTimer);
|
|
821
|
+
this.#autoScaleTimer = undefined;
|
|
822
|
+
}
|
|
823
|
+
await Promise.all([...this.clusters.values()].map((cluster) => this.#shutdownCluster(cluster, timeoutMs)));
|
|
824
|
+
this.clusters.clear();
|
|
825
|
+
this.#spawned = false;
|
|
826
|
+
}
|
|
827
|
+
async#shutdownCluster(cluster, timeoutMs) {
|
|
828
|
+
const child = cluster.process;
|
|
829
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
830
|
+
return;
|
|
831
|
+
await new Promise((resolve) => {
|
|
832
|
+
let timer;
|
|
833
|
+
const finish = () => {
|
|
834
|
+
if (timer)
|
|
835
|
+
clearTimeout(timer);
|
|
836
|
+
resolve();
|
|
837
|
+
};
|
|
838
|
+
child.once("exit", finish);
|
|
839
|
+
try {
|
|
840
|
+
child.kill("SIGTERM");
|
|
841
|
+
} catch {
|
|
842
|
+
child.off("exit", finish);
|
|
843
|
+
finish();
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
timer = setTimeout(() => {
|
|
847
|
+
try {
|
|
848
|
+
child.kill("SIGKILL");
|
|
849
|
+
} catch {}
|
|
850
|
+
}, timeoutMs);
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
killAll() {
|
|
854
|
+
if (this.#autoScaleTimer) {
|
|
855
|
+
clearInterval(this.#autoScaleTimer);
|
|
856
|
+
this.#autoScaleTimer = undefined;
|
|
857
|
+
}
|
|
858
|
+
for (const cluster of this.clusters.values()) {
|
|
859
|
+
try {
|
|
860
|
+
cluster.process.kill();
|
|
861
|
+
} catch {}
|
|
862
|
+
}
|
|
863
|
+
this.clusters.clear();
|
|
864
|
+
this.#spawned = false;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
639
867
|
|
|
640
868
|
// packages/sharding/src/index.ts
|
|
869
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
870
|
+
|
|
641
871
|
class ShardManager {
|
|
642
872
|
shards = new Map;
|
|
643
873
|
get shardCount() {
|
|
@@ -646,6 +876,8 @@ class ShardManager {
|
|
|
646
876
|
#options;
|
|
647
877
|
#resolved = false;
|
|
648
878
|
#destroyed = false;
|
|
879
|
+
#auto;
|
|
880
|
+
#autoScaleTimer;
|
|
649
881
|
constructor(options) {
|
|
650
882
|
if (!options.token?.trim())
|
|
651
883
|
throw new TypeError("A Discord bot token is required.");
|
|
@@ -654,6 +886,9 @@ class ShardManager {
|
|
|
654
886
|
const count = options.shardCount === "auto" || options.shardCount === undefined ? 1 : options.shardCount;
|
|
655
887
|
if (!Number.isInteger(count) || count < 1)
|
|
656
888
|
throw new RangeError("Shard count must be a positive integer.");
|
|
889
|
+
if (options.autoScaleInterval !== undefined && (!Number.isInteger(options.autoScaleInterval) || options.autoScaleInterval < 1000))
|
|
890
|
+
throw new RangeError("autoScaleInterval must be an integer of at least 1000 milliseconds.");
|
|
891
|
+
this.#auto = options.shardCount === "auto";
|
|
657
892
|
this.#options = { ...options };
|
|
658
893
|
if (options.shardCount !== "auto")
|
|
659
894
|
this.#initialize(count);
|
|
@@ -673,24 +908,56 @@ class ShardManager {
|
|
|
673
908
|
return data.shards;
|
|
674
909
|
}
|
|
675
910
|
async connect() {
|
|
911
|
+
if (this.#autoScaleTimer) {
|
|
912
|
+
clearInterval(this.#autoScaleTimer);
|
|
913
|
+
this.#autoScaleTimer = undefined;
|
|
914
|
+
}
|
|
676
915
|
await this.#ensureInitialized();
|
|
677
916
|
for (const [id, shard] of this.shards) {
|
|
678
917
|
try {
|
|
679
918
|
await shard.connect();
|
|
680
919
|
} catch (error) {
|
|
681
920
|
this.destroy();
|
|
682
|
-
throw new Error(`Failed to connect shard ${id}.`, {
|
|
921
|
+
throw new Error(`Failed to connect shard ${id}.`, {
|
|
922
|
+
cause: error
|
|
923
|
+
});
|
|
683
924
|
}
|
|
684
925
|
if (id + 1 < this.shardCount && this.#options.spawnDelay && this.#options.spawnDelay > 0)
|
|
685
|
-
await
|
|
926
|
+
await sleep2(this.#options.spawnDelay);
|
|
927
|
+
}
|
|
928
|
+
if (this.#options.autoScaleInterval && !this.#autoScaleTimer) {
|
|
929
|
+
this.#autoScaleTimer = setInterval(() => {
|
|
930
|
+
this.checkAutoScale();
|
|
931
|
+
}, this.#options.autoScaleInterval);
|
|
686
932
|
}
|
|
687
933
|
}
|
|
934
|
+
async checkAutoScale() {
|
|
935
|
+
if (!this.#auto)
|
|
936
|
+
return;
|
|
937
|
+
try {
|
|
938
|
+
const recommended = await this.fetchRecommendedShardCount();
|
|
939
|
+
if (recommended !== this.shardCount) {
|
|
940
|
+
await this.reshard(recommended);
|
|
941
|
+
}
|
|
942
|
+
} catch (error) {
|
|
943
|
+
this.#options.onAutoScaleError?.(error);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
async reshard(count) {
|
|
947
|
+
this.#options.shardCount = count;
|
|
948
|
+
this.destroy();
|
|
949
|
+
await this.connect();
|
|
950
|
+
}
|
|
688
951
|
destroy() {
|
|
689
952
|
for (const shard of this.shards.values())
|
|
690
953
|
shard.close();
|
|
691
954
|
this.shards.clear();
|
|
692
955
|
this.#resolved = false;
|
|
693
956
|
this.#destroyed = true;
|
|
957
|
+
if (this.#autoScaleTimer) {
|
|
958
|
+
clearInterval(this.#autoScaleTimer);
|
|
959
|
+
this.#autoScaleTimer = undefined;
|
|
960
|
+
}
|
|
694
961
|
}
|
|
695
962
|
getShardIdForGuild(guildId) {
|
|
696
963
|
const id = BigInt(guildId);
|
|
@@ -728,8 +995,9 @@ class ShardManager {
|
|
|
728
995
|
}
|
|
729
996
|
}
|
|
730
997
|
export {
|
|
998
|
+
ClusterManager,
|
|
731
999
|
ShardBus,
|
|
732
1000
|
ShardManager
|
|
733
1001
|
};
|
|
734
1002
|
|
|
735
|
-
//# debugId=
|
|
1003
|
+
//# debugId=F2AF359ADCA0121464756E2164756E21
|