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.
Files changed (79) hide show
  1. package/dist/builders/attachment.d.ts +1 -0
  2. package/dist/builders/clone.d.ts +10 -0
  3. package/dist/builders/commands.d.ts +1 -1
  4. package/dist/builders/components.d.ts +24 -5
  5. package/dist/builders/index.js +64 -17
  6. package/dist/builders/index.js.map +10 -9
  7. package/dist/collection/index.d.ts +1 -0
  8. package/dist/collection/index.js +297 -19
  9. package/dist/collection/index.js.map +5 -4
  10. package/dist/collection/redis.d.ts +98 -0
  11. package/dist/core/collector.d.ts +8 -0
  12. package/dist/core/events.d.ts +1 -0
  13. package/dist/core/index.d.ts +3 -0
  14. package/dist/core/index.js +6213 -4716
  15. package/dist/core/index.js.map +46 -39
  16. package/dist/core/permissions.d.ts +3 -402
  17. package/dist/core/redis-cache.d.ts +85 -0
  18. package/dist/formatters/index.d.ts +5 -1
  19. package/dist/formatters/index.js +7 -3
  20. package/dist/formatters/index.js.map +3 -3
  21. package/dist/handlers/index.js +11 -2
  22. package/dist/handlers/index.js.map +3 -3
  23. package/dist/index.js +8272 -6085
  24. package/dist/index.js.map +64 -58
  25. package/dist/managers/application.d.ts +10 -6
  26. package/dist/managers/base.d.ts +4 -1
  27. package/dist/managers/guild.d.ts +3 -1
  28. package/dist/managers/index.d.ts +6 -1
  29. package/dist/managers/index.js +365 -103
  30. package/dist/managers/index.js.map +19 -22
  31. package/dist/managers/member.d.ts +3 -1
  32. package/dist/managers/message.d.ts +13 -4
  33. package/dist/managers/thread.d.ts +3 -3
  34. package/dist/rest/decoder.d.ts +2 -0
  35. package/dist/rest/errors.d.ts +5 -2
  36. package/dist/rest/headers.d.ts +31 -0
  37. package/dist/rest/identity.d.ts +31 -0
  38. package/dist/rest/index.d.ts +97 -6
  39. package/dist/rest/index.js +1350 -220
  40. package/dist/rest/index.js.map +16 -13
  41. package/dist/rest/limiter.d.ts +75 -10
  42. package/dist/rest/redis.d.ts +103 -22
  43. package/dist/rest/scheduler.d.ts +13 -0
  44. package/dist/rest/store.d.ts +135 -12
  45. package/dist/rest/telemetry.d.ts +151 -0
  46. package/dist/rest/webhook.d.ts +18 -6
  47. package/dist/sharding/bus.d.ts +4 -2
  48. package/dist/sharding/cluster.d.ts +1 -1
  49. package/dist/sharding/health.d.ts +1 -1
  50. package/dist/sharding/host.d.ts +6 -0
  51. package/dist/sharding/identify.d.ts +2 -0
  52. package/dist/sharding/index.d.ts +6 -0
  53. package/dist/sharding/index.js +205 -39
  54. package/dist/sharding/index.js.map +14 -14
  55. package/dist/sharding/remote.d.ts +18 -2
  56. package/dist/sharding/supervisor.d.ts +1 -1
  57. package/dist/sharding/transport.d.ts +6 -5
  58. package/dist/sharding/worker.d.ts +11 -1
  59. package/dist/structures/base.d.ts +16 -5
  60. package/dist/structures/index.d.ts +1 -1
  61. package/dist/structures/index.js +57 -91
  62. package/dist/structures/index.js.map +10 -18
  63. package/dist/structures/options.d.ts +3 -2
  64. package/dist/structures/permissions.d.ts +409 -0
  65. package/dist/structures/resources.d.ts +4 -1
  66. package/dist/types/gateway.d.ts +4 -0
  67. package/dist/types/index.d.ts +14 -4
  68. package/dist/types/index.js.map +3 -3
  69. package/dist/utils/args.d.ts +3 -1
  70. package/dist/utils/index.d.ts +6 -1
  71. package/dist/utils/index.js +17 -10
  72. package/dist/utils/index.js.map +4 -4
  73. package/dist/voice/index.d.ts +7 -1
  74. package/dist/voice/index.js +33 -10
  75. package/dist/voice/index.js.map +3 -3
  76. package/dist/ws/index.d.ts +2 -0
  77. package/dist/ws/index.js +32 -4
  78. package/dist/ws/index.js.map +5 -5
  79. package/package.json +19 -16
@@ -2,7 +2,7 @@
2
2
  // packages/sharding/package.json
3
3
  var package_default = {
4
4
  name: "@lunibee/sharding",
5
- version: "0.3.1",
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
- this.#emit(event ?? "dispatch", data);
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
- this.#onDisconnect = () => this.#failAll("The parent went away.");
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)?.delete(handler);
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
- return this.on(type, async (message) => {
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 = Number.POSITIVE_INFINITY;
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
- #unauthenticated = 0;
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: { nonce: randomBytes(16).toString("hex") }
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
- if (this.#unauthenticated >= (this.#options.maxUnauthenticated ?? 16)) {
2403
- ws.close(RemoteClose.TooManyConnections, "Too many connections.");
2404
- return;
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.#unauthenticated++;
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 ?? 1e4);
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
- ws.data.counted = false;
2501
- this.#unauthenticated--;
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.#checkReady();
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
- const message = raw;
3137
- if (!message || typeof message !== "object" || typeof message.type !== "string" || typeof message.source !== "number" || message.target !== null && !Number.isInteger(message.target))
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
- if (this.#infos.size === 0)
3283
- return false;
3284
- for (const cluster of this.#infos.values())
3285
- if (cluster.state !== "ready")
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
- return true;
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.#options.shardCount ?? 1;
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=7D2FE3134CAB1F3364756E2164756E21
4277
+ //# debugId=8AD6CE4D87FD51CB64756E2164756E21