lunibee 0.3.1 → 0.3.2
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/builders/attachment.d.ts +1 -0
- package/dist/builders/clone.d.ts +10 -0
- package/dist/builders/commands.d.ts +1 -1
- package/dist/builders/components.d.ts +24 -5
- package/dist/builders/index.js +64 -17
- package/dist/builders/index.js.map +10 -9
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.js +297 -19
- package/dist/collection/index.js.map +5 -4
- package/dist/collection/redis.d.ts +98 -0
- package/dist/core/collector.d.ts +8 -0
- package/dist/core/events.d.ts +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +6213 -4716
- package/dist/core/index.js.map +46 -39
- package/dist/core/permissions.d.ts +3 -402
- package/dist/core/redis-cache.d.ts +85 -0
- package/dist/formatters/index.d.ts +5 -1
- package/dist/formatters/index.js +7 -3
- package/dist/formatters/index.js.map +3 -3
- package/dist/handlers/index.js +11 -2
- package/dist/handlers/index.js.map +3 -3
- package/dist/index.js +8272 -6085
- package/dist/index.js.map +64 -58
- package/dist/managers/application.d.ts +10 -6
- package/dist/managers/base.d.ts +4 -1
- package/dist/managers/guild.d.ts +3 -1
- package/dist/managers/index.d.ts +6 -1
- package/dist/managers/index.js +365 -103
- package/dist/managers/index.js.map +19 -22
- package/dist/managers/member.d.ts +3 -1
- package/dist/managers/message.d.ts +13 -4
- package/dist/managers/thread.d.ts +3 -3
- package/dist/rest/decoder.d.ts +2 -0
- package/dist/rest/errors.d.ts +5 -2
- package/dist/rest/headers.d.ts +31 -0
- package/dist/rest/identity.d.ts +31 -0
- package/dist/rest/index.d.ts +97 -6
- package/dist/rest/index.js +1350 -220
- package/dist/rest/index.js.map +16 -13
- package/dist/rest/limiter.d.ts +75 -10
- package/dist/rest/redis.d.ts +103 -22
- package/dist/rest/scheduler.d.ts +13 -0
- package/dist/rest/store.d.ts +135 -12
- package/dist/rest/telemetry.d.ts +151 -0
- package/dist/rest/webhook.d.ts +18 -6
- package/dist/sharding/bus.d.ts +4 -2
- package/dist/sharding/cluster.d.ts +1 -1
- package/dist/sharding/health.d.ts +1 -1
- package/dist/sharding/host.d.ts +6 -0
- package/dist/sharding/identify.d.ts +2 -0
- package/dist/sharding/index.d.ts +6 -0
- package/dist/sharding/index.js +205 -39
- package/dist/sharding/index.js.map +14 -14
- package/dist/sharding/remote.d.ts +18 -2
- package/dist/sharding/supervisor.d.ts +1 -1
- package/dist/sharding/transport.d.ts +6 -5
- package/dist/sharding/worker.d.ts +11 -1
- package/dist/structures/base.d.ts +16 -5
- package/dist/structures/index.d.ts +1 -1
- package/dist/structures/index.js +57 -91
- package/dist/structures/index.js.map +10 -18
- package/dist/structures/options.d.ts +3 -2
- package/dist/structures/permissions.d.ts +409 -0
- package/dist/structures/resources.d.ts +4 -1
- package/dist/types/gateway.d.ts +4 -0
- package/dist/types/index.d.ts +14 -4
- package/dist/types/index.js.map +3 -3
- package/dist/utils/args.d.ts +3 -1
- package/dist/utils/index.d.ts +6 -1
- package/dist/utils/index.js +17 -10
- package/dist/utils/index.js.map +4 -4
- package/dist/voice/index.d.ts +7 -1
- package/dist/voice/index.js +33 -10
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/index.d.ts +2 -0
- package/dist/ws/index.js +32 -4
- package/dist/ws/index.js.map +5 -5
- package/package.json +19 -16
package/dist/sharding/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// packages/sharding/package.json
|
|
3
3
|
var package_default = {
|
|
4
4
|
name: "@lunibee/sharding",
|
|
5
|
-
version: "0.3.
|
|
5
|
+
version: "0.3.2",
|
|
6
6
|
type: "module",
|
|
7
7
|
module: "./src/index.ts",
|
|
8
8
|
types: "./src/index.ts",
|
|
@@ -606,6 +606,7 @@ class WebSocketTransport {
|
|
|
606
606
|
decoded = this.#decoder.push(event.data);
|
|
607
607
|
} catch (error) {
|
|
608
608
|
this.#handlers.onError(this.#asError(error));
|
|
609
|
+
this.#failDecoding(generation);
|
|
609
610
|
return;
|
|
610
611
|
}
|
|
611
612
|
if (!(decoded instanceof Promise)) {
|
|
@@ -623,6 +624,7 @@ class WebSocketTransport {
|
|
|
623
624
|
if (!this.#owns(generation))
|
|
624
625
|
return;
|
|
625
626
|
this.#handlers.onError(this.#asError(error));
|
|
627
|
+
this.#failDecoding(generation);
|
|
626
628
|
});
|
|
627
629
|
});
|
|
628
630
|
socket.addEventListener("close", (event) => {
|
|
@@ -651,6 +653,10 @@ class WebSocketTransport {
|
|
|
651
653
|
return false;
|
|
652
654
|
}
|
|
653
655
|
}
|
|
656
|
+
#failDecoding(generation) {
|
|
657
|
+
if (this.#owns(generation))
|
|
658
|
+
this.close(4000, "decoder failure");
|
|
659
|
+
}
|
|
654
660
|
close(code = 1000, reason = "") {
|
|
655
661
|
const socket = this.#socket;
|
|
656
662
|
if (!socket)
|
|
@@ -993,6 +999,13 @@ class Gateway {
|
|
|
993
999
|
constructor(options) {
|
|
994
1000
|
if (!options.token?.trim())
|
|
995
1001
|
throw new TypeError("A Gateway token is required.");
|
|
1002
|
+
for (const key of [
|
|
1003
|
+
"reconnectBaseDelay",
|
|
1004
|
+
"reconnectMaxDelay",
|
|
1005
|
+
"connectTimeout"
|
|
1006
|
+
])
|
|
1007
|
+
if (options[key] !== undefined && !(options[key] > 0))
|
|
1008
|
+
throw new RangeError(`Gateway ${key} must be greater than 0.`);
|
|
996
1009
|
const resolvedIntents = resolveGatewayIntents(options.intents);
|
|
997
1010
|
if (!Number.isInteger(resolvedIntents) || resolvedIntents < 0)
|
|
998
1011
|
throw new TypeError("Gateway intents must be a non-negative integer.");
|
|
@@ -1001,6 +1014,7 @@ class Gateway {
|
|
|
1001
1014
|
shardCount: 1,
|
|
1002
1015
|
reconnect: true,
|
|
1003
1016
|
maxReconnectAttempts: Infinity,
|
|
1017
|
+
connectTimeout: 30000,
|
|
1004
1018
|
reconnectBaseDelay: 1000,
|
|
1005
1019
|
reconnectMaxDelay: 30000,
|
|
1006
1020
|
heartbeatAckTimeout: 1e4,
|
|
@@ -1076,7 +1090,7 @@ class Gateway {
|
|
|
1076
1090
|
this.#reconnect.cancel();
|
|
1077
1091
|
this.#closed = false;
|
|
1078
1092
|
this.#setState("CONNECT" /* Connect */);
|
|
1079
|
-
const connectURL = this.#options.compress ? url.includes("compress=") ? url : `${url}&compress=zlib-stream` : url;
|
|
1093
|
+
const connectURL = this.#options.compress ? url.includes("compress=") ? url : `${url}${url.includes("?") ? "&" : "?"}compress=zlib-stream` : url;
|
|
1080
1094
|
return this.#reconnect.attempt(() => this.#open(connectURL));
|
|
1081
1095
|
}
|
|
1082
1096
|
close() {
|
|
@@ -1162,6 +1176,8 @@ class Gateway {
|
|
|
1162
1176
|
const count = this.#options.shardCount;
|
|
1163
1177
|
if (count <= 1)
|
|
1164
1178
|
return true;
|
|
1179
|
+
if (!/^\d{1,20}$/.test(guildId))
|
|
1180
|
+
return false;
|
|
1165
1181
|
return Number((BigInt(guildId) >> 22n) % BigInt(count)) === this.#options.shardId;
|
|
1166
1182
|
}
|
|
1167
1183
|
requestGuildMembers(data) {
|
|
@@ -1177,8 +1193,16 @@ class Gateway {
|
|
|
1177
1193
|
return;
|
|
1178
1194
|
this.#token = this.#session.beginConnection();
|
|
1179
1195
|
const result = this.#transport.connect(url);
|
|
1180
|
-
if (result.ok)
|
|
1196
|
+
if (result.ok) {
|
|
1197
|
+
const token = this.#token;
|
|
1198
|
+
clearTimeout(this.#connectTimer);
|
|
1199
|
+
this.#connectTimer = setTimeout(() => {
|
|
1200
|
+
if (token === this.#token && !this.#closed)
|
|
1201
|
+
this.#transport.close(4000, "connect timeout");
|
|
1202
|
+
}, this.#options.connectTimeout);
|
|
1203
|
+
this.#connectTimer.unref?.();
|
|
1181
1204
|
return;
|
|
1205
|
+
}
|
|
1182
1206
|
const failure = this.#normalizeError(result.error);
|
|
1183
1207
|
this.#emitError(failure);
|
|
1184
1208
|
if (this.#options.reconnect) {
|
|
@@ -1188,7 +1212,9 @@ class Gateway {
|
|
|
1188
1212
|
this.#settleConnect(failure);
|
|
1189
1213
|
}
|
|
1190
1214
|
}
|
|
1215
|
+
#connectTimer;
|
|
1191
1216
|
#onOpen() {
|
|
1217
|
+
clearTimeout(this.#connectTimer);
|
|
1192
1218
|
this.#heartbeat.watch();
|
|
1193
1219
|
this.#emit("open", undefined);
|
|
1194
1220
|
this.#settleConnect();
|
|
@@ -1256,7 +1282,8 @@ class Gateway {
|
|
|
1256
1282
|
this.#emit("RAW", { event: event ?? "UNKNOWN", data });
|
|
1257
1283
|
if (this.#closed)
|
|
1258
1284
|
return;
|
|
1259
|
-
|
|
1285
|
+
if (event == null || /^[A-Z][A-Z0-9_]*$/.test(event))
|
|
1286
|
+
this.#emit(event ?? "dispatch", data);
|
|
1260
1287
|
}
|
|
1261
1288
|
#handleHello(interval) {
|
|
1262
1289
|
this.#setState(this.#session.canResume ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
|
|
@@ -1319,6 +1346,7 @@ class Gateway {
|
|
|
1319
1346
|
this.#reconnect.settle(error);
|
|
1320
1347
|
}
|
|
1321
1348
|
#clearTimers() {
|
|
1349
|
+
clearTimeout(this.#connectTimer);
|
|
1322
1350
|
this.#reconnect.cancel();
|
|
1323
1351
|
this.#heartbeat.stop();
|
|
1324
1352
|
}
|
|
@@ -1474,13 +1502,19 @@ class ClusterWorker {
|
|
|
1474
1502
|
#onDisconnect;
|
|
1475
1503
|
#counter = 0;
|
|
1476
1504
|
#closed = false;
|
|
1477
|
-
constructor(context, endpoint = process) {
|
|
1505
|
+
constructor(context, endpoint = process, options = {}) {
|
|
1478
1506
|
if (typeof endpoint.send !== "function")
|
|
1479
1507
|
throw new Error("ClusterWorker needs an IPC channel: run it in a process forked by ClusterManager.");
|
|
1480
1508
|
this.context = context;
|
|
1481
1509
|
this.#endpoint = endpoint;
|
|
1482
1510
|
this.#onMessage = (frame) => this.#handle(frame);
|
|
1483
|
-
|
|
1511
|
+
const exitOnLoss = options.exitOnParentLoss ?? endpoint === process;
|
|
1512
|
+
const exit = options.exit ?? ((code) => process.exit(code));
|
|
1513
|
+
this.#onDisconnect = () => {
|
|
1514
|
+
this.#failAll("The parent went away.");
|
|
1515
|
+
if (exitOnLoss && !this.#closed)
|
|
1516
|
+
exit(1);
|
|
1517
|
+
};
|
|
1484
1518
|
endpoint.on("message", this.#onMessage);
|
|
1485
1519
|
endpoint.on("disconnect", this.#onDisconnect);
|
|
1486
1520
|
}
|
|
@@ -1591,6 +1625,7 @@ class BroadcastChannelTransport {
|
|
|
1591
1625
|
}
|
|
1592
1626
|
var BUS_FRAME = "__lunibeeBus";
|
|
1593
1627
|
var PEER_DOWN_FRAME = "__lunibeePeerDown";
|
|
1628
|
+
var LOCAL = new WeakMap;
|
|
1594
1629
|
|
|
1595
1630
|
class IpcTransport {
|
|
1596
1631
|
#endpoint;
|
|
@@ -1611,9 +1646,18 @@ class IpcTransport {
|
|
|
1611
1646
|
this.#down?.(record[PEER_DOWN_FRAME]);
|
|
1612
1647
|
};
|
|
1613
1648
|
endpoint.on("message", this.#listener);
|
|
1649
|
+
let local = LOCAL.get(endpoint);
|
|
1650
|
+
if (!local)
|
|
1651
|
+
LOCAL.set(endpoint, local = new Set);
|
|
1652
|
+
local.add(this);
|
|
1614
1653
|
}
|
|
1615
1654
|
post(message) {
|
|
1616
1655
|
this.#endpoint.send({ [BUS_FRAME]: message });
|
|
1656
|
+
for (const peer of LOCAL.get(this.#endpoint) ?? [])
|
|
1657
|
+
if (peer !== this) {
|
|
1658
|
+
const copy = structuredClone(message);
|
|
1659
|
+
queueMicrotask(() => peer.#message?.(copy));
|
|
1660
|
+
}
|
|
1617
1661
|
}
|
|
1618
1662
|
onMessage(listener) {
|
|
1619
1663
|
this.#message = listener;
|
|
@@ -1623,6 +1667,7 @@ class IpcTransport {
|
|
|
1623
1667
|
}
|
|
1624
1668
|
close() {
|
|
1625
1669
|
this.#endpoint.off("message", this.#listener);
|
|
1670
|
+
LOCAL.get(this.#endpoint)?.delete(this);
|
|
1626
1671
|
}
|
|
1627
1672
|
}
|
|
1628
1673
|
function defaultTransport(name) {
|
|
@@ -1645,6 +1690,7 @@ class ShardBus {
|
|
|
1645
1690
|
#transport;
|
|
1646
1691
|
#maxPending;
|
|
1647
1692
|
#handlers = new Map;
|
|
1693
|
+
#responders = new Map;
|
|
1648
1694
|
#errorHandlers = new Set;
|
|
1649
1695
|
#shardId;
|
|
1650
1696
|
#namespace;
|
|
@@ -1675,7 +1721,13 @@ class ShardBus {
|
|
|
1675
1721
|
return this;
|
|
1676
1722
|
}
|
|
1677
1723
|
off(type, handler) {
|
|
1678
|
-
this.#handlers.get(type)
|
|
1724
|
+
const handlers = this.#handlers.get(type);
|
|
1725
|
+
handlers?.delete(handler);
|
|
1726
|
+
const responder = this.#responders.get(type)?.get(handler);
|
|
1727
|
+
if (responder) {
|
|
1728
|
+
handlers?.delete(responder);
|
|
1729
|
+
this.#responders.get(type).delete(handler);
|
|
1730
|
+
}
|
|
1679
1731
|
return this;
|
|
1680
1732
|
}
|
|
1681
1733
|
onError(handler) {
|
|
@@ -1697,7 +1749,7 @@ class ShardBus {
|
|
|
1697
1749
|
return this.#publish(null, type, data);
|
|
1698
1750
|
}
|
|
1699
1751
|
respond(type, handler) {
|
|
1700
|
-
|
|
1752
|
+
const wrapper = async (message) => {
|
|
1701
1753
|
if (!message.expectsReply)
|
|
1702
1754
|
return;
|
|
1703
1755
|
const reply = { requestId: message.id };
|
|
@@ -1707,7 +1759,12 @@ class ShardBus {
|
|
|
1707
1759
|
reply.error = error instanceof Error ? error.message : String(error);
|
|
1708
1760
|
}
|
|
1709
1761
|
this.#publish(message.source, REPLY_TYPE, reply);
|
|
1710
|
-
}
|
|
1762
|
+
};
|
|
1763
|
+
let byType = this.#responders.get(type);
|
|
1764
|
+
if (!byType)
|
|
1765
|
+
this.#responders.set(type, byType = new Map);
|
|
1766
|
+
byType.set(handler, wrapper);
|
|
1767
|
+
return this.on(type, wrapper);
|
|
1711
1768
|
}
|
|
1712
1769
|
async request(target, type, data, timeoutMs = 5000) {
|
|
1713
1770
|
if (!Number.isInteger(target) || target < 0)
|
|
@@ -1868,7 +1925,7 @@ class ShardSupervisor {
|
|
|
1868
1925
|
recent.push(now);
|
|
1869
1926
|
this.#restarts.set(id, recent);
|
|
1870
1927
|
const base = Math.min(maxDelay, restartDelay * backoff ** (recent.length - 1));
|
|
1871
|
-
return Math.round(base * (1 + jitter * this.#random()));
|
|
1928
|
+
return Math.min(maxDelay, Math.round(base * (1 + jitter * this.#random())));
|
|
1872
1929
|
}
|
|
1873
1930
|
reset(id) {
|
|
1874
1931
|
if (id === undefined)
|
|
@@ -1896,6 +1953,7 @@ class IdentifyCoordinator {
|
|
|
1896
1953
|
#remaining;
|
|
1897
1954
|
#total;
|
|
1898
1955
|
#resetAt = Number.POSITIVE_INFINITY;
|
|
1956
|
+
#period = 24 * 60 * 60 * 1000;
|
|
1899
1957
|
#timer;
|
|
1900
1958
|
#timerAt = Number.POSITIVE_INFINITY;
|
|
1901
1959
|
#closed = false;
|
|
@@ -1922,6 +1980,9 @@ class IdentifyCoordinator {
|
|
|
1922
1980
|
this.#total = options.budget.total;
|
|
1923
1981
|
this.#remaining = options.budget.remaining;
|
|
1924
1982
|
this.#resetAt = this.#now() + options.budget.resetAfter;
|
|
1983
|
+
this.#period = options.budget.period ?? this.#period;
|
|
1984
|
+
if (!(this.#period > 0))
|
|
1985
|
+
throw new RangeError("budget.period must be positive.");
|
|
1925
1986
|
}
|
|
1926
1987
|
}
|
|
1927
1988
|
get remaining() {
|
|
@@ -1995,7 +2056,8 @@ class IdentifyCoordinator {
|
|
|
1995
2056
|
const now = this.#now();
|
|
1996
2057
|
if (this.#total !== undefined && now >= this.#resetAt) {
|
|
1997
2058
|
this.#remaining = this.#total;
|
|
1998
|
-
this.#resetAt
|
|
2059
|
+
while (now >= this.#resetAt)
|
|
2060
|
+
this.#resetAt += this.#period;
|
|
1999
2061
|
}
|
|
2000
2062
|
let next = Number.POSITIVE_INFINITY;
|
|
2001
2063
|
for (const key of this.#keys.values()) {
|
|
@@ -2007,11 +2069,6 @@ class IdentifyCoordinator {
|
|
|
2007
2069
|
}
|
|
2008
2070
|
if (this.#remaining !== undefined && this.#remaining <= 0) {
|
|
2009
2071
|
const waiter = key.queue[0];
|
|
2010
|
-
if (this.#resetAt === Number.POSITIVE_INFINITY) {
|
|
2011
|
-
key.queue.shift();
|
|
2012
|
-
waiter.fail(new Error("The session start limit is exhausted."));
|
|
2013
|
-
continue;
|
|
2014
|
-
}
|
|
2015
2072
|
this.#onBudgetWait?.({
|
|
2016
2073
|
shardId: waiter.shardId,
|
|
2017
2074
|
waitMs: this.#resetAt - now
|
|
@@ -2265,7 +2322,9 @@ class RemoteHosts {
|
|
|
2265
2322
|
#watchdog;
|
|
2266
2323
|
#epoch = 0;
|
|
2267
2324
|
#ping = 0;
|
|
2268
|
-
#
|
|
2325
|
+
#pending = new Set;
|
|
2326
|
+
#pendingByAddress = new Map;
|
|
2327
|
+
#rates = new Map;
|
|
2269
2328
|
constructor(options, hooks) {
|
|
2270
2329
|
if (typeof options.secret !== "string" || options.secret.length < 16)
|
|
2271
2330
|
throw new RangeError("remote.secret must be a string of at least 16 characters.");
|
|
@@ -2303,8 +2362,17 @@ class RemoteHosts {
|
|
|
2303
2362
|
hostname: hostname ?? "127.0.0.1",
|
|
2304
2363
|
...tls ? { tls } : {},
|
|
2305
2364
|
fetch: (request, server) => {
|
|
2365
|
+
const peer = server.requestIP(request)?.address;
|
|
2366
|
+
const address = this.#options.clientAddress?.(request, peer) ?? peer ?? "unknown";
|
|
2367
|
+
if (!this.#allowConnection(address))
|
|
2368
|
+
return new Response("Too many connections.", {
|
|
2369
|
+
status: 429
|
|
2370
|
+
});
|
|
2306
2371
|
if (server.upgrade(request, {
|
|
2307
|
-
data: {
|
|
2372
|
+
data: {
|
|
2373
|
+
nonce: randomBytes(16).toString("hex"),
|
|
2374
|
+
address
|
|
2375
|
+
}
|
|
2308
2376
|
}))
|
|
2309
2377
|
return;
|
|
2310
2378
|
return new Response("Lunibee cluster coordinator", {
|
|
@@ -2398,14 +2466,46 @@ class RemoteHosts {
|
|
|
2398
2466
|
return false;
|
|
2399
2467
|
}
|
|
2400
2468
|
}
|
|
2469
|
+
#allowConnection(address) {
|
|
2470
|
+
const rate = this.#options.connectionsPerSecond ?? 10;
|
|
2471
|
+
const now = Date.now();
|
|
2472
|
+
let bucket = this.#rates.get(address);
|
|
2473
|
+
if (!bucket) {
|
|
2474
|
+
bucket = { tokens: rate, at: now };
|
|
2475
|
+
this.#rates.set(address, bucket);
|
|
2476
|
+
if (this.#rates.size > 1e4)
|
|
2477
|
+
this.#rates.delete(this.#rates.keys().next().value);
|
|
2478
|
+
}
|
|
2479
|
+
bucket.tokens = Math.min(rate, bucket.tokens + (now - bucket.at) / 1000 * rate);
|
|
2480
|
+
bucket.at = now;
|
|
2481
|
+
if (bucket.tokens < 1)
|
|
2482
|
+
return false;
|
|
2483
|
+
bucket.tokens--;
|
|
2484
|
+
return true;
|
|
2485
|
+
}
|
|
2486
|
+
#evict(victim) {
|
|
2487
|
+
this.#uncount(victim);
|
|
2488
|
+
clearTimeout(victim.data.helloTimer);
|
|
2489
|
+
victim.close(RemoteClose.TooManyConnections, "Too many connections.");
|
|
2490
|
+
}
|
|
2401
2491
|
#open(ws) {
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2492
|
+
const address = ws.data.address;
|
|
2493
|
+
if ((this.#pendingByAddress.get(address) ?? 0) >= (this.#options.maxUnauthenticatedPerAddress ?? 4)) {
|
|
2494
|
+
for (const other of this.#pending)
|
|
2495
|
+
if (other.data.address === address) {
|
|
2496
|
+
this.#evict(other);
|
|
2497
|
+
break;
|
|
2498
|
+
}
|
|
2405
2499
|
}
|
|
2406
|
-
this.#
|
|
2500
|
+
if (this.#pending.size >= (this.#options.maxUnauthenticated ?? 16)) {
|
|
2501
|
+
const oldest = this.#pending.values().next().value;
|
|
2502
|
+
if (oldest)
|
|
2503
|
+
this.#evict(oldest);
|
|
2504
|
+
}
|
|
2505
|
+
this.#pending.add(ws);
|
|
2506
|
+
this.#pendingByAddress.set(address, (this.#pendingByAddress.get(address) ?? 0) + 1);
|
|
2407
2507
|
ws.data.counted = true;
|
|
2408
|
-
ws.data.helloTimer = setTimeout(() => ws.close(RemoteClose.HelloTimeout, "No hello."), this.#options.helloTimeout ??
|
|
2508
|
+
ws.data.helloTimer = setTimeout(() => ws.close(RemoteClose.HelloTimeout, "No hello."), this.#options.helloTimeout ?? 3000);
|
|
2409
2509
|
ws.send(JSON.stringify({
|
|
2410
2510
|
t: "challenge",
|
|
2411
2511
|
nonce: ws.data.nonce,
|
|
@@ -2496,10 +2596,15 @@ class RemoteHosts {
|
|
|
2496
2596
|
waiter.check();
|
|
2497
2597
|
}
|
|
2498
2598
|
#uncount(ws) {
|
|
2499
|
-
if (ws.data.counted)
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2599
|
+
if (!ws.data.counted)
|
|
2600
|
+
return;
|
|
2601
|
+
ws.data.counted = false;
|
|
2602
|
+
this.#pending.delete(ws);
|
|
2603
|
+
const left = (this.#pendingByAddress.get(ws.data.address) ?? 1) - 1;
|
|
2604
|
+
if (left > 0)
|
|
2605
|
+
this.#pendingByAddress.set(ws.data.address, left);
|
|
2606
|
+
else
|
|
2607
|
+
this.#pendingByAddress.delete(ws.data.address);
|
|
2503
2608
|
}
|
|
2504
2609
|
#closed(ws, reason) {
|
|
2505
2610
|
clearTimeout(ws.data.helloTimer);
|
|
@@ -2901,6 +3006,9 @@ class ClusterManager {
|
|
|
2901
3006
|
this.#infos.set(id, info);
|
|
2902
3007
|
this.clusters.set(id, info);
|
|
2903
3008
|
this.#allReady = false;
|
|
3009
|
+
child.on("error", (error) => this.#emit("error", new Error(`Cluster ${id} process error: ${error.message}`, {
|
|
3010
|
+
cause: error
|
|
3011
|
+
})));
|
|
2904
3012
|
child.on("message", (frame) => this.#onFrame(info, child, frame));
|
|
2905
3013
|
child.once("exit", (code, signal) => this.#onExit(info, child, code, signal));
|
|
2906
3014
|
const timeout = this.#options.startTimeout;
|
|
@@ -2942,7 +3050,7 @@ class ClusterManager {
|
|
|
2942
3050
|
this.#announceDown(info.id, info.shards);
|
|
2943
3051
|
if (this.#options.restartOnExit === false) {
|
|
2944
3052
|
info.state = "failed";
|
|
2945
|
-
this.#
|
|
3053
|
+
this.#rejectReady(info, "restartOnExit is false");
|
|
2946
3054
|
return;
|
|
2947
3055
|
}
|
|
2948
3056
|
const delay = this.#supervisor.next(info.id);
|
|
@@ -2952,9 +3060,10 @@ class ClusterManager {
|
|
|
2952
3060
|
this.#options.onGiveUp?.(info);
|
|
2953
3061
|
} catch {}
|
|
2954
3062
|
this.#emit("clusterGiveUp", info);
|
|
2955
|
-
this.#checkReady();
|
|
2956
3063
|
if (this.#options.reassignOnGiveUp)
|
|
2957
3064
|
this.#reassign(info);
|
|
3065
|
+
else
|
|
3066
|
+
this.#rejectReady(info, "restarts exhausted");
|
|
2958
3067
|
return;
|
|
2959
3068
|
}
|
|
2960
3069
|
info.state = "restarting";
|
|
@@ -2979,12 +3088,14 @@ class ClusterManager {
|
|
|
2979
3088
|
const target = [...this.#infos.values()].filter((cluster) => cluster.state === "ready").sort((a, b) => a.shards.length - b.shards.length)[0];
|
|
2980
3089
|
if (!target) {
|
|
2981
3090
|
this.#emit("error", new Error(`No ready cluster can take over shards [${shardIds}] of cluster ${failed.id}.`));
|
|
3091
|
+
this.#rejectReady(failed, "no ready cluster to take them");
|
|
2982
3092
|
return;
|
|
2983
3093
|
}
|
|
2984
3094
|
try {
|
|
2985
3095
|
await this.requestCluster(target.id, REQUEST_ASSIGN, { shardIds }, { timeoutMs: this.#options.startTimeout ?? 120000 });
|
|
2986
3096
|
} catch (error) {
|
|
2987
3097
|
this.#emit("error", new Error(`Cluster ${target.id} could not take over shards [${shardIds}]: ${error instanceof Error ? error.message : String(error)}`));
|
|
3098
|
+
this.#rejectReady(failed, "reassignment failed");
|
|
2988
3099
|
return;
|
|
2989
3100
|
}
|
|
2990
3101
|
if (this.#stopping || failed.state !== "failed")
|
|
@@ -2998,6 +3109,7 @@ class ClusterManager {
|
|
|
2998
3109
|
from: failed.id,
|
|
2999
3110
|
to: target.id
|
|
3000
3111
|
});
|
|
3112
|
+
this.#checkReady();
|
|
3001
3113
|
}
|
|
3002
3114
|
#clearStartTimer(id) {
|
|
3003
3115
|
const timer = this.#startTimers.get(id);
|
|
@@ -3133,9 +3245,20 @@ class ClusterManager {
|
|
|
3133
3245
|
throw new RangeError(`${what} is larger than maxMessageBytes (${this.#maxBytes}).`);
|
|
3134
3246
|
}
|
|
3135
3247
|
#onBus(from, raw) {
|
|
3136
|
-
|
|
3137
|
-
if (!message || typeof message !== "object" || typeof message.type !== "string" || typeof message.
|
|
3248
|
+
let message = raw;
|
|
3249
|
+
if (!message || typeof message !== "object" || typeof message.type !== "string" || message.type.length === 0 || message.type.length > 256 || message.id === undefined || typeof message.id !== "string" || message.id.length > 256 || !Number.isInteger(message.source) || message.target !== null && !Number.isInteger(message.target))
|
|
3138
3250
|
return;
|
|
3251
|
+
if (!from.shards.includes(message.source))
|
|
3252
|
+
return;
|
|
3253
|
+
const { type, id, source, target, data, expectsReply } = message;
|
|
3254
|
+
message = {
|
|
3255
|
+
type,
|
|
3256
|
+
id,
|
|
3257
|
+
source,
|
|
3258
|
+
target,
|
|
3259
|
+
data,
|
|
3260
|
+
...expectsReply === undefined ? {} : { expectsReply }
|
|
3261
|
+
};
|
|
3139
3262
|
if (this.#tooBig(message.data)) {
|
|
3140
3263
|
this.#emit("error", new Error(`Cluster ${from.id} sent a shard-bus message of more than ${this.#maxBytes} bytes; it was not relayed.`));
|
|
3141
3264
|
return;
|
|
@@ -3279,12 +3402,21 @@ class ClusterManager {
|
|
|
3279
3402
|
});
|
|
3280
3403
|
}
|
|
3281
3404
|
#isReady() {
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3405
|
+
let ready = 0;
|
|
3406
|
+
for (const cluster of this.#infos.values()) {
|
|
3407
|
+
if (cluster.state === "ready")
|
|
3408
|
+
ready++;
|
|
3409
|
+
else if (!(cluster.state === "failed" && cluster.shards.length === 0))
|
|
3286
3410
|
return false;
|
|
3287
|
-
|
|
3411
|
+
}
|
|
3412
|
+
return ready > 0;
|
|
3413
|
+
}
|
|
3414
|
+
#rejectReady(failed, why) {
|
|
3415
|
+
if (failed.shards.length === 0)
|
|
3416
|
+
return;
|
|
3417
|
+
const error = new Error(`Cluster ${failed.id} failed and shards [${failed.shards}] have no owner (${why}).`);
|
|
3418
|
+
for (const waiter of [...this.#readyWaiters])
|
|
3419
|
+
waiter.reject(error);
|
|
3288
3420
|
}
|
|
3289
3421
|
#checkReady() {
|
|
3290
3422
|
if (!this.#isReady())
|
|
@@ -3448,6 +3580,7 @@ class ClusterHost {
|
|
|
3448
3580
|
#watchdog;
|
|
3449
3581
|
#retry;
|
|
3450
3582
|
#delay;
|
|
3583
|
+
#busyAttempts = 0;
|
|
3451
3584
|
#closed = true;
|
|
3452
3585
|
#connected = false;
|
|
3453
3586
|
#firstConnect;
|
|
@@ -3500,6 +3633,7 @@ class ClusterHost {
|
|
|
3500
3633
|
if (!this.#closed)
|
|
3501
3634
|
return Promise.resolve();
|
|
3502
3635
|
this.#closed = false;
|
|
3636
|
+
this.#busyAttempts = 0;
|
|
3503
3637
|
return new Promise((resolve, reject) => {
|
|
3504
3638
|
this.#firstConnect = { resolve, reject };
|
|
3505
3639
|
this.#open();
|
|
@@ -3550,12 +3684,31 @@ class ClusterHost {
|
|
|
3550
3684
|
return false;
|
|
3551
3685
|
}
|
|
3552
3686
|
}
|
|
3687
|
+
#refuseSpawn(socket, data) {
|
|
3688
|
+
if (typeof data !== "string" || data.length > 65536)
|
|
3689
|
+
return;
|
|
3690
|
+
let frame;
|
|
3691
|
+
try {
|
|
3692
|
+
frame = JSON.parse(data);
|
|
3693
|
+
} catch {
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
if (frame?.t !== "spawn" || !Number.isInteger(frame.cluster) || !Number.isInteger(frame.epoch))
|
|
3697
|
+
return;
|
|
3698
|
+
this.#send(socket, {
|
|
3699
|
+
t: "error",
|
|
3700
|
+
cluster: frame.cluster,
|
|
3701
|
+
epoch: frame.epoch,
|
|
3702
|
+
message: "This host refused the spawn request (malformed or not allowed)."
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3553
3705
|
#message(socket, data) {
|
|
3554
3706
|
if (this.#socket !== socket)
|
|
3555
3707
|
return;
|
|
3556
3708
|
const message = parseToHost(data);
|
|
3557
3709
|
if (!message) {
|
|
3558
3710
|
this.#emit("error", new Error("The coordinator sent a malformed frame."));
|
|
3711
|
+
this.#refuseSpawn(socket, data);
|
|
3559
3712
|
return;
|
|
3560
3713
|
}
|
|
3561
3714
|
this.#lastRx = Date.now();
|
|
@@ -3687,6 +3840,11 @@ class ClusterHost {
|
|
|
3687
3840
|
return;
|
|
3688
3841
|
const refused = code === RemoteClose.AuthFailed || code === RemoteClose.UnknownHost || code === RemoteClose.DuplicateHost || code === RemoteClose.VersionMismatch;
|
|
3689
3842
|
const first = this.#firstConnect;
|
|
3843
|
+
const busy = code === RemoteClose.TooManyConnections || code === RemoteClose.HelloTimeout;
|
|
3844
|
+
if (first && busy && ++this.#busyAttempts < (this.#options.connectAttempts ?? 10)) {
|
|
3845
|
+
this.#retryLater();
|
|
3846
|
+
return;
|
|
3847
|
+
}
|
|
3690
3848
|
if (first) {
|
|
3691
3849
|
this.#firstConnect = undefined;
|
|
3692
3850
|
this.#closed = true;
|
|
@@ -3695,6 +3853,9 @@ class ClusterHost {
|
|
|
3695
3853
|
}
|
|
3696
3854
|
if (refused)
|
|
3697
3855
|
this.#emit("error", new Error(`The coordinator refused this host (${code}: ${reason}).`));
|
|
3856
|
+
this.#retryLater();
|
|
3857
|
+
}
|
|
3858
|
+
#retryLater() {
|
|
3698
3859
|
const wait = Math.round(this.#delay * (1 + Math.random() * 0.2));
|
|
3699
3860
|
this.#delay = Math.min(this.#options.maxReconnectDelay ?? 30000, this.#delay * 2);
|
|
3700
3861
|
this.#retry = setTimeout(() => {
|
|
@@ -3757,6 +3918,7 @@ class ShardManager {
|
|
|
3757
3918
|
#resolved = false;
|
|
3758
3919
|
#destroyed = false;
|
|
3759
3920
|
#auto;
|
|
3921
|
+
#fixedCount;
|
|
3760
3922
|
#autoScaleTimer;
|
|
3761
3923
|
#total;
|
|
3762
3924
|
#listeners = new Map;
|
|
@@ -3799,6 +3961,7 @@ class ShardManager {
|
|
|
3799
3961
|
if (options.handshakeTimeout !== undefined && (!Number.isFinite(options.handshakeTimeout) || options.handshakeTimeout < 0))
|
|
3800
3962
|
throw new RangeError("handshakeTimeout must be a non-negative number of milliseconds.");
|
|
3801
3963
|
this.#total = count;
|
|
3964
|
+
this.#fixedCount = count;
|
|
3802
3965
|
this.#auto = options.shardCount === "auto";
|
|
3803
3966
|
this.#options = {
|
|
3804
3967
|
spawnDelay: ShardManager.IDENTIFY_INTERVAL,
|
|
@@ -3830,7 +3993,7 @@ class ShardManager {
|
|
|
3830
3993
|
}
|
|
3831
3994
|
await this.#ensureInitialized();
|
|
3832
3995
|
await this.#start([...this.shards].filter(([, gateway]) => needsConnection(gateway)).map(([id]) => id));
|
|
3833
|
-
if (this.#options.autoScaleInterval && !this.#autoScaleTimer) {
|
|
3996
|
+
if (this.#options.autoScaleInterval && this.#auto && !this.#autoScaleTimer) {
|
|
3834
3997
|
this.#autoScaleTimer = setInterval(() => {
|
|
3835
3998
|
this.checkAutoScale();
|
|
3836
3999
|
}, this.#options.autoScaleInterval);
|
|
@@ -3905,6 +4068,8 @@ class ShardManager {
|
|
|
3905
4068
|
this.#setStatus(id, "connecting");
|
|
3906
4069
|
const results = await Promise.allSettled(round.map((id) => this.shards.get(id).connect()));
|
|
3907
4070
|
const failed = results.findIndex((result) => result.status === "rejected");
|
|
4071
|
+
if (limit)
|
|
4072
|
+
limit.remaining = Math.max(0, limit.remaining - results.filter((r) => r.status === "fulfilled").length);
|
|
3908
4073
|
if (failed !== -1) {
|
|
3909
4074
|
if (destroyOnFailure)
|
|
3910
4075
|
this.destroy();
|
|
@@ -4041,7 +4206,7 @@ class ShardManager {
|
|
|
4041
4206
|
return;
|
|
4042
4207
|
if (this.#destroyed)
|
|
4043
4208
|
this.#destroyed = false;
|
|
4044
|
-
const count = this.#options.shardCount === "auto" ? await this.fetchRecommendedShardCount() : this.#
|
|
4209
|
+
const count = this.#options.shardCount === "auto" ? await this.fetchRecommendedShardCount() : this.#fixedCount;
|
|
4045
4210
|
this.#initialize(count);
|
|
4046
4211
|
}
|
|
4047
4212
|
#initialize(count) {
|
|
@@ -4079,6 +4244,7 @@ class ShardManager {
|
|
|
4079
4244
|
this.#setStatus(id, "unstable");
|
|
4080
4245
|
this.#emit("shardUnstable", id, info);
|
|
4081
4246
|
});
|
|
4247
|
+
this.#emit("shardCreate", id, gateway);
|
|
4082
4248
|
return gateway;
|
|
4083
4249
|
}
|
|
4084
4250
|
}
|
|
@@ -4108,4 +4274,4 @@ export {
|
|
|
4108
4274
|
validateShardIds
|
|
4109
4275
|
};
|
|
4110
4276
|
|
|
4111
|
-
//# debugId=
|
|
4277
|
+
//# debugId=8AD6CE4D87FD51CB64756E2164756E21
|