@bakit/gateway 2.1.9 → 3.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 CHANGED
@@ -1,486 +1,619 @@
1
- import { WebSocket } from 'ws';
1
+ import EventEmitter from 'events';
2
2
  import { createInflate, constants } from 'zlib';
3
- import { attachEventBus, Collection, createQueue } from '@bakit/utils';
4
- import { GatewayOpcodes, GatewayCloseCodes, GatewayDispatchEvents } from 'discord-api-types/gateway';
5
- import { fileURLToPath } from 'url';
3
+ import { TextDecoder } from 'util';
4
+ import { randomInt, randomUUID } from 'crypto';
5
+ import WebSocket from 'ws';
6
+ import { GatewayOpcodes, GatewayDispatchEvents, GatewayCloseCodes } from 'discord-api-types/v10';
6
7
  import { fork } from 'child_process';
7
- import '@bakit/rest';
8
+ import { dirname, resolve } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { isCommonJS, Collection, Queue } from '@bakit/utils';
11
+ import { REST } from '@bakit/rest';
8
12
 
9
- // src/lib/shard.ts
10
- var DEFAULT_SHARD_OPTIONS = {
11
- gateway: {
12
- baseURL: "wss://gateway.discord.gg",
13
- version: 10
14
- }
15
- }, ShardState = {
16
- /** Not connected. */
17
- Idle: 0,
18
- /** Initialzing connection. */
19
- Connecting: 1,
20
- /** Connected to gateway and identified. */
21
- Ready: 2,
22
- /** Resuming the session. */
23
- Resuming: 3,
24
- /** Disconnected the connection. */
25
- Disconnected: 4
26
- }, ShardStrategy = {
27
- /** No decision yet (default on close). */
28
- Unknown: 1,
29
- /** Reconnect with IDENTIFY (new session). */
30
- Reconnect: 2,
31
- /** Reconnect and try RESUME. */
32
- Resume: 3,
33
- /** Do not reconnect. */
34
- Shutdown: 4
35
- };
36
- function createShard(options) {
37
- let resolvedOptions = { ...DEFAULT_SHARD_OPTIONS, ...options }, textDecoder = new TextDecoder(), state = ShardState.Idle, strategy = ShardStrategy.Unknown, ws, resumeGatewayURL, inflater, decompressBuffer = [], sessionId, lastSequence, lastHeartbeatSent = -1, lastHeartbeatAcknowledged = -1, missedHeartbeats = 0, reconnectTimeout, heartbeatTimeout, heartbeatInterval, self = attachEventBus({
38
- send,
39
- connect,
40
- disconnect,
41
- identify,
42
- get id() {
43
- return options.id;
44
- },
45
- get state() {
46
- return state;
47
- },
48
- get latency() {
49
- return lastHeartbeatSent === -1 || lastHeartbeatAcknowledged === -1 ? -1 : lastHeartbeatAcknowledged - lastHeartbeatSent;
50
- }
51
- });
52
- function init() {
53
- if (state !== ShardState.Idle && state !== ShardState.Disconnected) {
54
- self.emit("error", new Error("Shard is already connected or connecting."));
55
- return;
56
- }
57
- if (strategy === ShardStrategy.Shutdown)
58
- return;
59
- state = ShardState.Connecting;
60
- let { gateway } = resolvedOptions, baseURL = isResumable() && resumeGatewayURL ? resumeGatewayURL : gateway.baseURL, url = new URL(baseURL);
61
- url.searchParams.set("v", gateway.version.toString()), url.searchParams.set("encoding", "json"), url.searchParams.set("compress", "zlib-stream"), ws = new WebSocket(url.toString(), {
62
- perMessageDeflate: false
63
- }), inflater = createInflate({
64
- flush: constants.Z_SYNC_FLUSH
65
- }), inflater.on("data", onInflate), inflater.on("error", (err) => {
66
- self.emit("error", err), ws?.terminate();
67
- }), ws.on("message", onMessage), ws.on("close", onClose), ws.on("error", (err) => self.emit("error", err));
68
- }
69
- function cleanup() {
70
- heartbeatInterval && (clearInterval(heartbeatInterval), heartbeatInterval = void 0), heartbeatTimeout && (clearTimeout(heartbeatTimeout), heartbeatTimeout = void 0), reconnectTimeout && (clearTimeout(reconnectTimeout), reconnectTimeout = void 0), inflater && (inflater.destroy(), inflater = void 0), ws && (ws.readyState !== WebSocket.CLOSED && ws.terminate(), ws.removeAllListeners(), ws = void 0), decompressBuffer = [], missedHeartbeats = 0, lastHeartbeatSent = -1, lastHeartbeatAcknowledged = -1;
71
- }
72
- function connect() {
73
- return new Promise((resolve) => {
74
- state !== ShardState.Idle && state !== ShardState.Disconnected || (strategy = ShardStrategy.Unknown, self.once("ready", () => resolve()), init());
13
+ // src/lib/Shard.ts
14
+ var MIN_HEARTBEAT_INTERVAL = 1e3, MAX_HEARTBEAT_INTERVAL = 6e4, SAFE_HEARTBEAT_INTERVAL = 45e3, ShardState = /* @__PURE__ */ ((ShardState2) => (ShardState2[ShardState2.Idle = 0] = "Idle", ShardState2[ShardState2.Connecting = 1] = "Connecting", ShardState2[ShardState2.Ready = 2] = "Ready", ShardState2[ShardState2.Resuming = 3] = "Resuming", ShardState2[ShardState2.Disconnecting = 4] = "Disconnecting", ShardState2[ShardState2.Disconnected = 5] = "Disconnected", ShardState2))(ShardState || {}), ShardStrategy = /* @__PURE__ */ ((ShardStrategy2) => (ShardStrategy2[ShardStrategy2.Resume = 0] = "Resume", ShardStrategy2[ShardStrategy2.Reconnect = 1] = "Reconnect", ShardStrategy2[ShardStrategy2.Shutdown = 2] = "Shutdown", ShardStrategy2))(ShardStrategy || {}), Shard = class extends EventEmitter {
15
+ constructor(id, options) {
16
+ super();
17
+ this.id = id;
18
+ this.options = options;
19
+ }
20
+ #state = 0 /* Idle */;
21
+ #ws;
22
+ #inflater;
23
+ #textDecoder = new TextDecoder();
24
+ #decompressBuffer = [];
25
+ #sessionId;
26
+ #lastSequence;
27
+ #resumeGatewayURL;
28
+ #lastHeartbeatSent = -1;
29
+ #lastHeartbeatAck = -1;
30
+ #missedHeartbeats = 0;
31
+ #heartbeatInterval;
32
+ #heartbeatTimeout;
33
+ #reconnectTimeout;
34
+ #strategy;
35
+ get state() {
36
+ return this.#state;
37
+ }
38
+ get latency() {
39
+ return this.#lastHeartbeatSent === -1 || this.#lastHeartbeatAck === -1 ? -1 : this.#lastHeartbeatAck - this.#lastHeartbeatSent;
40
+ }
41
+ get resumable() {
42
+ let hasSessionId = this.#sessionId !== void 0, hasSequence = this.#lastSequence !== void 0;
43
+ return this.#strategy === 0 /* Resume */ && hasSequence && hasSessionId;
44
+ }
45
+ async connect() {
46
+ if (this.#state !== 0 /* Idle */ && this.#state !== 5 /* Disconnected */)
47
+ throw new Error("Shard already connecting or connected");
48
+ return new Promise((resolve2, reject) => {
49
+ let cleanup = () => {
50
+ this.off("error", onError), this.off("ready", onReady);
51
+ }, onReady = () => {
52
+ cleanup(), resolve2();
53
+ }, onError = (err) => {
54
+ cleanup(), reject(err);
55
+ };
56
+ this.once("ready", onReady), this.once("error", onError), this.#init();
75
57
  });
76
58
  }
77
- function disconnect(code = 1e3) {
78
- return new Promise((resolve) => {
79
- if (strategy = ShardStrategy.Shutdown, !ws) {
80
- resolve();
59
+ disconnect(code) {
60
+ return new Promise((resolve2) => {
61
+ if (this.#state = 4 /* Disconnecting */, this.#strategy = 2 /* Shutdown */, !this.#ws) {
62
+ resolve2();
81
63
  return;
82
64
  }
83
- ws.once("close", () => {
84
- resolve();
85
- }), ws.close(code);
65
+ this.#ws.once("close", () => {
66
+ resolve2();
67
+ }), this.#ws.close(code);
68
+ });
69
+ }
70
+ resume() {
71
+ this.resumable && (this.#state = 3 /* Resuming */, this.send({
72
+ op: GatewayOpcodes.Resume,
73
+ d: {
74
+ token: this.options.token,
75
+ session_id: this.#sessionId,
76
+ seq: this.#lastSequence
77
+ }
78
+ }));
79
+ }
80
+ identify() {
81
+ this.send({
82
+ op: GatewayOpcodes.Identify,
83
+ d: {
84
+ token: this.options.token,
85
+ intents: Number(this.options.intents),
86
+ properties: {
87
+ os: process.platform,
88
+ browser: "bakit",
89
+ device: "bakit"
90
+ },
91
+ shard: [this.id, this.options.total]
92
+ }
86
93
  });
87
94
  }
88
- function onMessage(data) {
89
- if (!inflater) {
95
+ send(payload) {
96
+ this.#ws?.readyState === WebSocket.OPEN && this.#ws.send(JSON.stringify(payload));
97
+ }
98
+ sendHeartbeat() {
99
+ if (this.#lastHeartbeatSent !== -1 && this.#lastHeartbeatAck < this.#lastHeartbeatSent ? this.#missedHeartbeats++ : this.#missedHeartbeats = 0, this.#missedHeartbeats >= 2) {
100
+ this.emit("debug", "Missed 2 heartbeats, reconnecting"), this.#ws?.terminate();
101
+ return;
102
+ }
103
+ this.send({
104
+ op: GatewayOpcodes.Heartbeat,
105
+ d: this.#lastSequence ?? null
106
+ }), this.#lastHeartbeatSent = Date.now();
107
+ }
108
+ #init() {
109
+ this.#state = 1 /* Connecting */, this.#strategy ??= 1 /* Reconnect */;
110
+ let url = new URL(
111
+ this.#strategy === 0 /* Resume */ && this.#resumeGatewayURL ? this.#resumeGatewayURL : this.options.gateway.baseURL
112
+ );
113
+ url.searchParams.set("v", String(this.options.gateway.version)), url.searchParams.set("encoding", "json"), url.searchParams.set("compress", "zlib-stream"), this.#ws = new WebSocket(url, { perMessageDeflate: false }), this.#inflater = createInflate({ flush: constants.Z_SYNC_FLUSH }), this.#inflater.on("data", (chunk) => this.#onInflate(chunk)), this.#inflater.on("error", (err) => {
114
+ this.emit("error", err), this.#ws?.terminate();
115
+ }), this.#ws.on("message", (data) => this.#onMessage(data)), this.#ws.on("close", (code) => this.#onClose(code)), this.#ws.on("error", (err) => this.emit("error", err));
116
+ }
117
+ #onMessage(data) {
118
+ if (!this.#inflater) {
90
119
  try {
91
120
  let text = data.toString(), payload = JSON.parse(text);
92
- handlePayload(payload);
121
+ this.#handlePayload(payload);
93
122
  } catch (error) {
94
- self.emit("error", error);
123
+ this.emit("error", error);
95
124
  }
96
125
  return;
97
126
  }
98
127
  let buffer;
99
128
  Buffer.isBuffer(data) ? buffer = data : Array.isArray(data) ? buffer = Buffer.concat(data) : data instanceof ArrayBuffer ? buffer = Buffer.from(data) : buffer = Buffer.from(String(data));
100
129
  let hasSyncFlush = buffer.length >= 4 && buffer[buffer.length - 4] === 0 && buffer[buffer.length - 3] === 0 && buffer[buffer.length - 2] === 255 && buffer[buffer.length - 1] === 255;
101
- inflater.write(buffer, (writeError) => {
130
+ this.#inflater.write(buffer, (writeError) => {
102
131
  if (writeError) {
103
- self.emit("error", writeError);
132
+ this.emit("error", writeError);
104
133
  return;
105
134
  }
106
- hasSyncFlush && inflater?.flush(constants.Z_SYNC_FLUSH);
135
+ hasSyncFlush && this.#inflater?.flush(constants.Z_SYNC_FLUSH);
107
136
  });
108
137
  }
109
- function onInflate(chunk) {
110
- decompressBuffer.push(chunk);
138
+ #onInflate(chunk) {
139
+ this.#decompressBuffer.push(chunk);
140
+ let fullBuffer = Buffer.concat(this.#decompressBuffer);
111
141
  try {
112
- let fullBuffer = Buffer.concat(decompressBuffer), text = textDecoder.decode(fullBuffer), payload = JSON.parse(text);
113
- handlePayload(payload), decompressBuffer = [];
142
+ let text = this.#textDecoder.decode(fullBuffer), payload = JSON.parse(text);
143
+ this.#handlePayload(payload), this.#decompressBuffer = [];
114
144
  } catch (error) {
115
145
  if (error instanceof SyntaxError) {
116
- let fullBuffer = Buffer.concat(decompressBuffer), text = textDecoder.decode(fullBuffer);
146
+ let text = this.#textDecoder.decode(fullBuffer);
117
147
  if (text.includes("{") && !isValidJSON(text))
118
148
  return;
119
- self.emit("error", error), decompressBuffer = [];
120
- }
121
- }
122
- }
123
- function isValidJSON(str) {
124
- try {
125
- return JSON.parse(str), !0;
126
- } catch {
127
- return false;
128
- }
129
- }
130
- function onClose(code) {
131
- if (cleanup(), state = ShardState.Disconnected, self.emit("disconnect", code), strategy === ShardStrategy.Shutdown) {
132
- switch (code) {
133
- case GatewayCloseCodes.AuthenticationFailed:
134
- self.emit("error", new Error("Invalid token provided"));
135
- break;
136
- case GatewayCloseCodes.InvalidIntents:
137
- self.emit("error", new Error("Invalid intents provided"));
138
- break;
139
- case GatewayCloseCodes.DisallowedIntents:
140
- self.emit("error", new Error("Disallowed intents provided"));
141
- break;
149
+ this.emit("error", error), this.#decompressBuffer = [];
142
150
  }
143
- return;
144
151
  }
145
- strategy === ShardStrategy.Unknown && (strategy = getReconnectStrategy(code)), (strategy === ShardStrategy.Reconnect || strategy === ShardStrategy.Resume) && scheduleReconnect();
146
152
  }
147
- function handlePayload(payload) {
148
- switch (self.emit("raw", payload), payload.op) {
153
+ #handlePayload(payload) {
154
+ switch (this.emit("raw", payload), payload.op) {
149
155
  case GatewayOpcodes.Dispatch: {
150
- handleDispatch(payload);
156
+ this.#handleDispatch(payload);
151
157
  break;
152
158
  }
153
159
  case GatewayOpcodes.Hello: {
154
- startHeartbeat(payload.d.heartbeat_interval), isResumable() ? resume() : self.emit("requestIdentify");
160
+ this.#startHeartbeat(payload.d.heartbeat_interval), this.resumable ? this.resume() : this.emit("needIdentify");
155
161
  break;
156
162
  }
157
163
  case GatewayOpcodes.Heartbeat: {
158
- sendHeartbeat();
164
+ this.sendHeartbeat();
159
165
  break;
160
166
  }
161
167
  case GatewayOpcodes.HeartbeatAck: {
162
- lastHeartbeatAcknowledged = Date.now();
168
+ this.#lastHeartbeatAck = Date.now();
163
169
  break;
164
170
  }
165
171
  case GatewayOpcodes.InvalidSession: {
166
- payload.d ? strategy = ShardStrategy.Resume : (strategy = ShardStrategy.Reconnect, sessionId = void 0, lastSequence = void 0, resumeGatewayURL = void 0), self.emit("debug", `Invalid session (resumable=${isResumable()})`), ws?.terminate();
172
+ payload.d ? this.#strategy = 0 /* Resume */ : (this.#strategy = 1 /* Reconnect */, this.#sessionId = void 0, this.#lastSequence = void 0, this.#resumeGatewayURL = void 0), this.emit("debug", `Invalid session (resumable=${this.resumable})`), this.#ws?.terminate();
167
173
  break;
168
174
  }
169
175
  case GatewayOpcodes.Reconnect: {
170
- strategy = ShardStrategy.Resume, self.emit("debug", "Reconnecting to gateway"), ws?.terminate();
176
+ this.#strategy = 0 /* Resume */, this.emit("debug", "Reconnecting to gateway"), this.#ws?.terminate();
171
177
  break;
172
178
  }
173
179
  }
174
180
  }
175
- function handleDispatch(payload) {
176
- switch (lastSequence = payload.s, self.emit("dispatch", payload), payload.t) {
181
+ #handleDispatch(payload) {
182
+ switch (this.#lastSequence = payload.s, this.emit("dispatch", payload), payload.t) {
177
183
  case GatewayDispatchEvents.Ready: {
178
184
  let { d: data } = payload;
179
- state = ShardState.Ready, sessionId = data.session_id, resumeGatewayURL = data.resume_gateway_url, self.emit("ready", data);
185
+ this.#state = 2 /* Ready */, this.#sessionId = data.session_id, this.#resumeGatewayURL = data.resume_gateway_url, this.emit("ready", data);
180
186
  break;
181
187
  }
182
188
  case GatewayDispatchEvents.Resumed: {
183
- state = ShardState.Ready, strategy = ShardStrategy.Unknown, self.emit("resume");
189
+ this.#state = 2 /* Ready */, this.#strategy = void 0, this.emit("resume");
184
190
  break;
185
191
  }
186
192
  }
187
193
  }
188
- function isResumable() {
189
- let hasSessionId = sessionId !== void 0, hasSequence = lastSequence !== void 0;
190
- return strategy === ShardStrategy.Resume && hasSequence && hasSessionId;
194
+ #onClose(code) {
195
+ if (this.#cleanup(), this.#state = 5 /* Disconnected */, this.emit("disconnect", code), this.#strategy === 2 /* Shutdown */) {
196
+ switch (code) {
197
+ case GatewayCloseCodes.AuthenticationFailed:
198
+ this.emit("error", new Error("Invalid token provided"));
199
+ break;
200
+ case GatewayCloseCodes.InvalidIntents:
201
+ this.emit("error", new Error("Invalid intents provided"));
202
+ break;
203
+ case GatewayCloseCodes.DisallowedIntents:
204
+ this.emit("error", new Error("Disallowed intents provided"));
205
+ break;
206
+ }
207
+ return;
208
+ } else this.#strategy || (this.#strategy = this.#getStrategy(code));
209
+ (this.#strategy === 1 /* Reconnect */ || this.#strategy === 0 /* Resume */) && this.#scheduleReconnect();
191
210
  }
192
- function getReconnectStrategy(code) {
211
+ #getStrategy(code) {
193
212
  switch (code) {
194
213
  case GatewayCloseCodes.AuthenticationFailed:
195
214
  case GatewayCloseCodes.InvalidIntents:
196
215
  case GatewayCloseCodes.DisallowedIntents:
197
- return ShardStrategy.Shutdown;
216
+ return 2 /* Shutdown */;
198
217
  case GatewayCloseCodes.InvalidSeq:
199
218
  case GatewayCloseCodes.SessionTimedOut:
200
- return ShardStrategy.Reconnect;
219
+ return 1 /* Reconnect */;
201
220
  default:
202
- return ShardStrategy.Resume;
221
+ return 0 /* Resume */;
203
222
  }
204
223
  }
205
- function identify() {
206
- send({
207
- op: GatewayOpcodes.Identify,
208
- d: {
209
- token: resolvedOptions.token,
210
- intents: Number(resolvedOptions.intents),
211
- properties: {
212
- os: process.platform,
213
- browser: "bakit",
214
- device: "bakit"
215
- },
216
- shard: [resolvedOptions.id, resolvedOptions.total]
224
+ #scheduleReconnect(delay = 1e3) {
225
+ this.#reconnectTimeout || (this.#reconnectTimeout = setTimeout(() => {
226
+ this.#reconnectTimeout = void 0, this.#state = 0 /* Idle */, this.#init();
227
+ }, delay));
228
+ }
229
+ #startHeartbeat(interval) {
230
+ this.#heartbeatInterval && (clearInterval(this.#heartbeatInterval), this.#heartbeatInterval = void 0), (interval < MIN_HEARTBEAT_INTERVAL || interval > MAX_HEARTBEAT_INTERVAL) && (interval = SAFE_HEARTBEAT_INTERVAL);
231
+ let jitter = randomInt(0, 10) / 100, firstDelay = Math.floor(interval * jitter);
232
+ this.emit("debug", `Starting heartbeat (interval=${interval}ms, jitter=${firstDelay}ms)`), this.#heartbeatTimeout = setTimeout(() => {
233
+ this.sendHeartbeat(), this.#heartbeatInterval = setInterval(() => this.sendHeartbeat(), interval);
234
+ }, firstDelay);
235
+ }
236
+ #cleanup() {
237
+ clearTimeout(this.#reconnectTimeout), clearInterval(this.#heartbeatInterval), clearTimeout(this.#heartbeatTimeout), this.#inflater?.destroy(), this.#inflater = void 0, this.#ws?.removeAllListeners(), this.#ws = void 0, this.#decompressBuffer = [], this.#missedHeartbeats = 0;
238
+ }
239
+ };
240
+ function isValidJSON(str) {
241
+ try {
242
+ return JSON.parse(str), !0;
243
+ } catch {
244
+ return false;
245
+ }
246
+ }
247
+ var EVAL_TIMEOUT = 3e4, __filename$1 = fileURLToPath(import.meta.url), __dirname$1 = dirname(__filename$1);
248
+ function isDispatchPayload(payload) {
249
+ return payload.op === "dispatch";
250
+ }
251
+ function isIdentifyPayload(payload) {
252
+ return payload.op === "identify";
253
+ }
254
+ function isSendPayload(payload) {
255
+ return payload.op === "send";
256
+ }
257
+ function isEvalRequestPayload(payload) {
258
+ return payload.op === "eval";
259
+ }
260
+ function isEvalResponsePayload(payload) {
261
+ return payload.op === "evalResponse";
262
+ }
263
+ var ClusterProcess = class _ClusterProcess extends EventEmitter {
264
+ constructor(manager, id, options = {}) {
265
+ super();
266
+ this.manager = manager;
267
+ this.id = id;
268
+ this.setMaxListeners(0);
269
+ let entry = resolve(__dirname$1, isCommonJS() ? "cluster.cjs" : "cluster.js");
270
+ this.process = fork(entry, {
271
+ env: options.env,
272
+ execArgv: options.execArgv,
273
+ stdio: ["inherit", "inherit", "inherit", "ipc"]
274
+ }), this.#bindProcessEvents();
275
+ }
276
+ process;
277
+ #pendingEvals = /* @__PURE__ */ new Map();
278
+ get killed() {
279
+ return this.process.killed || !this.process.connected;
280
+ }
281
+ kill(signal = "SIGTERM") {
282
+ if (!this.killed) {
283
+ for (let [nonce, pending] of this.#pendingEvals)
284
+ clearTimeout(pending.timeout), pending.reject(new Error(`Process killed before eval completed (nonce: ${nonce})`));
285
+ this.#pendingEvals.clear(), this.process.kill(signal);
286
+ }
287
+ }
288
+ async eval(fn, ctx) {
289
+ let nonce = randomUUID();
290
+ return new Promise((resolve2, reject) => {
291
+ let timeoutId = setTimeout(() => {
292
+ this.#pendingEvals.delete(nonce), reject(new Error(`Eval timed out after ${EVAL_TIMEOUT}ms`));
293
+ }, EVAL_TIMEOUT);
294
+ this.#pendingEvals.set(nonce, {
295
+ resolve: resolve2,
296
+ reject,
297
+ timeout: timeoutId
298
+ });
299
+ let context;
300
+ try {
301
+ context = JSON.stringify(ctx ?? null);
302
+ } catch {
303
+ reject(new Error("Eval context is not serializable"));
304
+ return;
217
305
  }
306
+ let script = `(${fn.toString()})(cluster, ${context})`;
307
+ this.sendIPC({
308
+ op: "eval",
309
+ d: { nonce, script }
310
+ });
218
311
  });
219
312
  }
220
- function resume() {
221
- state = ShardState.Resuming, send({
222
- op: GatewayOpcodes.Resume,
223
- d: {
224
- token: resolvedOptions.token,
225
- session_id: sessionId,
226
- seq: lastSequence
227
- }
313
+ send(idOrPayload, payload) {
314
+ let hasShardId = typeof idOrPayload == "number" && payload !== void 0, shardId = hasShardId ? idOrPayload : void 0, data = hasShardId ? payload : idOrPayload;
315
+ this.sendIPC({
316
+ op: "send",
317
+ d: { shardId, data }
228
318
  });
229
319
  }
230
- function sendHeartbeat() {
231
- if (lastHeartbeatSent !== -1 && lastHeartbeatAcknowledged < lastHeartbeatSent ? missedHeartbeats++ : missedHeartbeats = 0, missedHeartbeats >= 2) {
232
- self.emit("debug", "Missed 2 heartbeats, reconnecting"), ws?.terminate();
233
- return;
234
- }
235
- send({
236
- op: GatewayOpcodes.Heartbeat,
237
- d: lastSequence ?? null
238
- }), lastHeartbeatSent = Date.now();
239
- }
240
- function startHeartbeat(interval) {
241
- heartbeatInterval && (clearInterval(heartbeatInterval), heartbeatInterval = void 0);
242
- let jitter = Math.random(), firstDelay = Math.floor(interval * jitter);
243
- self.emit("debug", `Starting heartbeat (interval=${interval}ms, jitter=${firstDelay}ms)`), heartbeatTimeout = setTimeout(() => {
244
- sendHeartbeat(), heartbeatInterval = setInterval(sendHeartbeat, interval);
245
- }, firstDelay);
320
+ sendIPC(message) {
321
+ if (!(!this.process.connected || this.process.killed))
322
+ try {
323
+ this.process.send(message, void 0, void 0, (err) => {
324
+ err && this.emit("error", err);
325
+ });
326
+ } catch (err) {
327
+ this.emit("error", err);
328
+ }
246
329
  }
247
- function scheduleReconnect(delay = 1e3) {
248
- reconnectTimeout || (reconnectTimeout = setTimeout(() => {
249
- reconnectTimeout = void 0, state = ShardState.Idle, init();
250
- }, delay));
330
+ identifyShard(id) {
331
+ this.sendIPC({
332
+ op: "identify",
333
+ d: id
334
+ });
251
335
  }
252
- function send(payload) {
253
- ws?.readyState === WebSocket.OPEN && ws.send(JSON.stringify(payload));
336
+ #bindProcessEvents() {
337
+ this.process.on("message", (message) => this.#handleIPC(message)), this.process.on("error", (err) => this.emit("error", err)), this.process.on("disconnect", () => this.emit("debug", "Process disconnected")), this.process.on("exit", (code) => {
338
+ for (let [nonce, pending] of this.#pendingEvals)
339
+ clearTimeout(pending.timeout), pending.reject(new Error(`Process exited (code: ${code}) before eval completed (nonce: ${nonce})`));
340
+ this.#pendingEvals.clear();
341
+ });
254
342
  }
255
- return self;
256
- }
257
- var GatewayWorkerState = {
258
- Idle: 0,
259
- Starting: 1,
260
- Ready: 2,
261
- Degraded: 3,
262
- Stopped: 4
263
- };
264
- function createWorker(options) {
265
- let shards = new Collection(), state = GatewayWorkerState.Idle, readyShards = /* @__PURE__ */ new Set(), self = attachEventBus({
266
- get id() {
267
- return options.id;
268
- },
269
- get shards() {
270
- return shards;
271
- },
272
- get shardIds() {
273
- return [...options.shards];
274
- },
275
- get latency() {
276
- let count = 0, sumLatency = shards.reduce((acc, shard) => shard.latency === -1 ? acc : (count++, acc + shard.latency), 0);
277
- return count === 0 ? -1 : sumLatency / count;
278
- },
279
- get state() {
280
- return state;
281
- },
282
- start,
283
- stop,
284
- broadcast
285
- });
286
- async function start() {
287
- if (!(state !== GatewayWorkerState.Idle && state !== GatewayWorkerState.Stopped)) {
288
- state = GatewayWorkerState.Starting;
289
- for (let id of options.shards) {
290
- let shard = createShard({
291
- id,
292
- token: options.token,
293
- intents: options.intents,
294
- total: options.total,
295
- gateway: {
296
- baseURL: options.gatewayURL,
297
- version: 10
298
- }
299
- });
300
- shards.set(id, shard), shard.on("ready", (data) => {
301
- if (readyShards.add(id), self.emit("shardReady", id, data), state !== GatewayWorkerState.Ready && readyShards.size === options.shards.length) {
302
- let wasDegraded = state === GatewayWorkerState.Degraded;
303
- state = GatewayWorkerState.Ready, self.emit(wasDegraded ? "resume" : "ready");
304
- }
305
- }), shard.on("disconnect", (code) => {
306
- readyShards.delete(id), self.emit("shardDisconnect", id, code), state !== GatewayWorkerState.Starting && state !== GatewayWorkerState.Degraded && readyShards.size < options.shards.length && (state = GatewayWorkerState.Degraded, self.emit("degrade", readyShards.size, options.shards.length));
307
- }), shard.on("raw", (payload) => self.emit("shardRaw", id, payload)), shard.on("dispatch", (payload) => self.emit("shardDispatch", id, payload)), shard.on("error", (err) => self.emit("error", err)), shard.on("debug", (msg) => self.emit("debug", `[Shard ${id}] ${msg}`)), shard.on("requestIdentify", () => self.emit("shardRequestIdentify", id)), shard.connect();
343
+ #handleIPC(message) {
344
+ if (this.#isValidPayload(message)) {
345
+ if (isDispatchPayload(message)) {
346
+ this.emit(message.t, ...message.d);
347
+ return;
348
+ }
349
+ if (isEvalResponsePayload(message)) {
350
+ this.#handleEvalResponse(message);
351
+ return;
308
352
  }
309
353
  }
310
354
  }
311
- async function stop(code = 1e3) {
312
- await Promise.all(shards.map((shard) => shard.disconnect(code))), state = GatewayWorkerState.Stopped, shards.clear(), readyShards.clear(), self.emit("stop");
355
+ #handleEvalResponse(payload) {
356
+ let pending = this.#pendingEvals.get(payload.d.nonce);
357
+ if (!pending) {
358
+ this.emit("debug", `Received eval response for unknown nonce: ${payload.d.nonce}`);
359
+ return;
360
+ }
361
+ if (pending.timeout && clearTimeout(pending.timeout), this.#pendingEvals.delete(payload.d.nonce), payload.d.success)
362
+ pending.resolve({
363
+ success: true,
364
+ data: payload.d.result,
365
+ cluster: this
366
+ });
367
+ else {
368
+ let error = new Error(payload.d.error ?? "Unknown eval error");
369
+ pending.resolve({
370
+ success: false,
371
+ error,
372
+ cluster: this
373
+ });
374
+ }
313
375
  }
314
- function broadcast(payload) {
315
- for (let shard of shards.values())
316
- shard.state === ShardState.Ready && shard.send(payload);
376
+ #isValidPayload(message) {
377
+ if (typeof message != "object" || message === null)
378
+ return false;
379
+ let payload = message;
380
+ return payload.op === "dispatch" || payload.op === "identify" || payload.op === "send" || payload.op === "eval" || payload.op === "evalResponse";
317
381
  }
318
- return self;
319
- }
320
- function bindWorkerToProcess(worker) {
321
- worker.on("shardRaw", (shardId, payload) => send("shardRaw", { shardId, payload })), worker.on("shardDispatch", (shardId, payload) => send("shardDispatch", { shardId, payload })), worker.on("shardReady", (shardId, payload) => send("shardReady", { shardId, payload })), worker.on("shardDisconnect", (shardId, code) => send("shardDisconnect", { shardId, code })), worker.on("shardRequestIdentify", (shardId) => send("shardRequestIdentify", { shardId })), worker.on("ready", () => send("ready", {})), worker.on("stop", () => send("stop", {})), worker.on("error", (error) => {
322
- send("workerError", {
323
- error: { message: error.message, stack: error.stack }
324
- });
325
- });
326
- function send(type, payload) {
327
- process.send && process.connected && process.send({ type, ...payload });
328
- }
329
- process.on("SIGINT", () => {
330
- }), process.on("SIGTERM", async () => {
331
- await worker.stop(1e3), process.exit(0);
332
- }), process.on("message", (message) => {
333
- switch (message.type) {
334
- case "identifyShard": {
335
- worker.shards.get(message.shardId)?.identify();
336
- break;
382
+ static bindProcess(cluster) {
383
+ let superEmit = cluster.emit.bind(cluster), safeSend = (message) => {
384
+ process.connected && process.send?.(message, void 0, void 0, (err) => {
385
+ err && cluster.emit("error", err);
386
+ });
387
+ };
388
+ cluster.emit = function(eventName, ...args) {
389
+ let result = superEmit(eventName, ...args);
390
+ return safeSend({
391
+ op: "dispatch",
392
+ t: eventName,
393
+ d: args
394
+ }), result;
395
+ };
396
+ let messageHandler = async (message) => {
397
+ if (isIdentifyPayload(message)) {
398
+ cluster.shards.get(message.d)?.identify();
399
+ return;
337
400
  }
338
- case "broadcast": {
339
- worker.broadcast(message.payload);
340
- break;
401
+ if (isSendPayload(message)) {
402
+ message.d.shardId !== void 0 ? cluster.send(message.d.shardId, message.d.data) : cluster.send(message.d.data);
403
+ return;
341
404
  }
342
- case "sendToShard": {
343
- worker.shards.get(message.shardId)?.send(message.payload);
344
- break;
405
+ if (isEvalRequestPayload(message)) {
406
+ await _ClusterProcess.#handleEvalRequest(cluster, message, safeSend);
407
+ return;
345
408
  }
409
+ };
410
+ process.on("message", messageHandler);
411
+ }
412
+ static async #handleEvalRequest(cluster, payload, safeSend) {
413
+ let { nonce, script } = payload.d, executeEval = async () => await new Function("cluster", `return ${script}`)(cluster), timeoutId;
414
+ try {
415
+ let evalPromise = executeEval(), timeoutPromise = new Promise((_, reject) => {
416
+ timeoutId = setTimeout(() => {
417
+ reject(new Error(`Eval execution timed out after ${EVAL_TIMEOUT}ms`));
418
+ }, EVAL_TIMEOUT);
419
+ }), result = await Promise.race([evalPromise, timeoutPromise]);
420
+ timeoutId && clearTimeout(timeoutId), safeSend({
421
+ op: "evalResponse",
422
+ d: {
423
+ nonce,
424
+ success: !0,
425
+ result
426
+ }
427
+ });
428
+ } catch (err) {
429
+ timeoutId && clearTimeout(timeoutId), safeSend({
430
+ op: "evalResponse",
431
+ d: {
432
+ nonce,
433
+ success: false,
434
+ result: void 0,
435
+ error: err instanceof Error ? err.message : String(err)
436
+ }
437
+ });
346
438
  }
347
- });
348
- }
349
- function getWorkerOptions() {
350
- let { WORKER_DATA } = process.env;
351
- if (!WORKER_DATA)
352
- throw new Error("WORKER_DATA is not set");
353
- return JSON.parse(WORKER_DATA);
354
- }
355
- var DEFAULT_WORKER_PATH = fileURLToPath(new URL("./services/worker.js", import.meta.url)), DEFAULT_GATEWAY_MANAGER_OPTIONS = {
356
- gatewayURL: "wss://gateway.discord.gg",
357
- totalShards: "auto",
358
- shardsPerWorker: 5
439
+ }
359
440
  };
360
- function createGatewayManager(options, rest) {
361
- let opts = {
362
- ...DEFAULT_GATEWAY_MANAGER_OPTIONS,
363
- ...options
364
- }, identifyQueue, workers = new Collection(), self = attachEventBus({
365
- get rest() {
366
- return rest;
367
- },
368
- spawn,
369
- broadcast,
370
- sendToWorker,
371
- sendToShard
372
- });
373
- async function spawn() {
374
- let gatewayBotInfo = await rest.get("/gateway/bot"), { session_start_limit: limit } = gatewayBotInfo, totalShards = opts.totalShards === "auto" ? gatewayBotInfo.shards : opts.totalShards, totalWorkers = Math.ceil(totalShards / opts.shardsPerWorker);
375
- if (limit.remaining < totalShards) {
376
- let error = new Error(
377
- [
378
- "Not enough remaining gateway sessions to spawn shards.",
379
- `Required: ${totalShards}`,
380
- `Remaining: ${limit.remaining}`,
381
- `Resets in: ${Math.ceil(limit.reset_after / 1e3)}s`
382
- ].join(" ")
383
- );
384
- self.emit("error", error);
385
- return;
441
+ var Cluster = class extends EventEmitter {
442
+ constructor(id, options) {
443
+ super();
444
+ this.id = id;
445
+ this.options = options;
446
+ }
447
+ shards = new Collection();
448
+ #readyCount = 0;
449
+ #starting = false;
450
+ get size() {
451
+ return this.shards.size;
452
+ }
453
+ get ready() {
454
+ return this.#readyCount === this.options.shards.length;
455
+ }
456
+ async spawn() {
457
+ if (!this.#starting) {
458
+ this.#starting = true, this.emit("debug", `Spawning ${this.options.shards.length} shards...`);
459
+ for (let i of this.options.shards)
460
+ await this.#spawnShard(i);
461
+ this.emit("debug", "All shards spawned");
386
462
  }
387
- identifyQueue && (identifyQueue.pause(), identifyQueue = void 0), identifyQueue = createQueue({
463
+ }
464
+ async shutdown(code = 1e3) {
465
+ this.emit("debug", "Shutting down cluster...");
466
+ let tasks = [];
467
+ for (let shard of this.shards.values())
468
+ tasks.push(shard.disconnect(code));
469
+ await Promise.allSettled(tasks), this.emit("debug", "Cluster shutdown complete");
470
+ }
471
+ broadcast(fn) {
472
+ for (let shard of this.shards.values())
473
+ fn(shard);
474
+ }
475
+ send(idOrPayload, payload) {
476
+ let hasId = typeof idOrPayload == "number" && payload !== void 0, shardId = hasId ? idOrPayload : void 0, data = hasId ? payload : idOrPayload;
477
+ shardId !== void 0 ? this.shards.get(shardId)?.send(data) : this.broadcast((shard) => shard.send(data));
478
+ }
479
+ async #spawnShard(id) {
480
+ let shard = new Shard(id, {
481
+ token: this.options.token,
482
+ intents: this.options.intents,
483
+ total: this.options.total,
484
+ gateway: this.options.gateway
485
+ });
486
+ this.#bindShardEvents(shard), this.shards.set(id, shard), this.emit("shardAdd", id), await shard.connect();
487
+ }
488
+ #bindShardEvents(shard) {
489
+ let id = shard.id;
490
+ shard.on("ready", () => {
491
+ this.#readyCount++, this.emit("debug", `Shard ${id} ready`), this.emit("shardReady", id), this.ready && this.emit("ready");
492
+ }), shard.on("resume", () => {
493
+ this.emit("debug", `Shard ${id} resumed`), this.emit("shardResume", id);
494
+ }), shard.on("disconnect", (code) => {
495
+ this.emit("debug", `Shard ${id} disconnected (${code})`), this.emit("shardDisconnect", id, code);
496
+ }), shard.on("error", (err) => {
497
+ this.emit("debug", `Shard ${id} error: ${err.message}`), this.emit("shardError", id, err);
498
+ }), shard.on("raw", (payload) => {
499
+ this.emit("raw", id, payload);
500
+ }), shard.on("dispatch", (payload) => {
501
+ this.emit("dispatch", id, payload);
502
+ }), shard.on("needIdentify", () => {
503
+ this.emit("needIdentify", id);
504
+ }), shard.on("debug", (msg) => {
505
+ this.emit("debug", `[Shard ${id}] ${msg}`);
506
+ });
507
+ }
508
+ };
509
+ var ShardingManager = class extends EventEmitter {
510
+ clusters = new Collection();
511
+ options;
512
+ rest;
513
+ #gatewayInfo;
514
+ #totalShards = 0;
515
+ #readyCount = 0;
516
+ #identifyQueue;
517
+ constructor(options, rest) {
518
+ super(), this.setMaxListeners(0), this.options = {
519
+ shardsPerCluster: 5,
520
+ totalShards: options.totalShards ?? "auto",
521
+ ...options
522
+ }, rest || (rest = new REST({ token: this.options.token })), this.rest = rest;
523
+ }
524
+ get totalClusters() {
525
+ let { shardsPerCluster } = this.options;
526
+ return shardsPerCluster <= 0 ? 0 : Math.ceil(this.totalShards / shardsPerCluster);
527
+ }
528
+ get totalShards() {
529
+ return this.#totalShards;
530
+ }
531
+ get ready() {
532
+ return this.#readyCount === this.totalClusters;
533
+ }
534
+ async spawn() {
535
+ this.#gatewayInfo = await this.rest.get("/gateway/bot");
536
+ let { session_start_limit: limit } = this.#gatewayInfo;
537
+ this.#totalShards = typeof this.options.totalShards == "number" ? this.options.totalShards : this.#gatewayInfo.shards, this.#identifyQueue = new Queue({
388
538
  concurrency: limit.max_concurrency,
389
539
  intervalCap: limit.max_concurrency,
390
540
  interval: 5e3
391
541
  });
392
- for (let i = 0; i < totalWorkers; i++) {
393
- let start = i * opts.shardsPerWorker, shards = Array.from({ length: Math.min(opts.shardsPerWorker, totalShards - start) }, (_, j) => start + j), workerOptions = {
394
- id: i,
395
- total: totalShards,
396
- token: options.token,
397
- intents: options.intents,
398
- shards,
399
- gatewayURL: opts.gatewayURL ?? "wss://gateway.discord.gg"
400
- };
401
- workers.set(i, spawnWorker(workerOptions));
402
- }
542
+ let { totalShards, totalClusters } = this;
543
+ this.emit("debug", `Spawning ${totalClusters} clusters (${totalShards} total shards)...`);
544
+ let promises = [];
545
+ for (let i = 0; i < totalClusters; i++)
546
+ promises.push(this.#spawnCluster(i));
547
+ await Promise.all(promises), this.emit("debug", "All clusters spawned");
403
548
  }
404
- async function shutdown(signal) {
405
- console.log(`Received ${signal}, shutting down...`), await Promise.all(
406
- [...workers.values()].map((child) => new Promise((resolve) => {
407
- child.once("exit", () => resolve()), child.kill("SIGTERM");
408
- }))
409
- ), process.exit(0);
410
- }
411
- process.once("SIGINT", shutdown), process.once("SIGTERM", shutdown);
412
- function spawnWorker(payload) {
413
- let child = fork(opts.workerPath ?? DEFAULT_WORKER_PATH, [], {
414
- env: {
415
- WORKER_DATA: JSON.stringify(payload)
416
- },
417
- stdio: "inherit"
418
- });
419
- return child.on("message", (msg) => {
420
- switch (msg.type) {
421
- case "shardRaw": {
422
- self.emit("shardRaw", payload.id, msg.shardId, msg.payload);
423
- break;
424
- }
425
- case "shardDispatch": {
426
- self.emit("shardDispatch", payload.id, msg.shardId, msg.payload);
427
- break;
428
- }
429
- case "shardReady": {
430
- self.emit("shardReady", payload.id, msg.shardId, msg.payload);
431
- break;
432
- }
433
- case "shardDisconnect": {
434
- self.emit("shardDisconnect", payload.id, msg.shardId, msg.code);
435
- break;
436
- }
437
- case "shardRequestIdentify": {
438
- identifyQueue?.add(async () => {
439
- child.connected && child.send({
440
- type: "identifyShard",
441
- shardId: msg.shardId
442
- });
443
- });
444
- break;
445
- }
446
- case "ready": {
447
- self.emit("workerReady", payload.id);
448
- break;
449
- }
450
- case "stop": {
451
- self.emit("workerStop", payload.id);
452
- break;
453
- }
454
- case "workerError": {
455
- self.emit("error", new Error(`[worker ${payload.id}] ${msg.error.message}`));
456
- break;
457
- }
458
- }
459
- }), child;
549
+ async kill(signal = "SIGTERM") {
550
+ this.emit("debug", "Shutting down all clusters...");
551
+ let tasks = [];
552
+ for (let cluster of this.clusters.values())
553
+ tasks.push(
554
+ new Promise((resolve2) => {
555
+ cluster.process.once("exit", () => resolve2()), cluster.kill(signal);
556
+ })
557
+ );
558
+ await Promise.all(tasks), this.emit("debug", "All clusters shut down");
460
559
  }
461
- function broadcast(payload) {
462
- for (let child of workers.values())
463
- child.connected && child.send({
464
- type: "broadcast",
465
- payload
466
- });
560
+ broadcast(payload) {
561
+ for (let cluster of this.clusters.values())
562
+ cluster.send(payload);
467
563
  }
468
- function sendToShard(shardId, payload) {
469
- let workerId = Math.floor(shardId / opts.shardsPerWorker), child = workers.get(workerId);
470
- return child?.connected ? (child.send({
471
- type: "sendToShard",
472
- shardId,
473
- payload
474
- }), true) : false;
475
- }
476
- function sendToWorker(workerId, payload) {
477
- let child = workers.get(workerId);
478
- return child?.connected ? (child.send({
479
- type: "broadcast",
480
- payload
481
- }), true) : false;
482
- }
483
- return self;
484
- }
564
+ async broadcastEval(fn) {
565
+ let promises = this.clusters.map((cluster) => cluster.eval(fn));
566
+ return Promise.all(promises);
567
+ }
568
+ requestIdentify(cluster, shardId) {
569
+ this.#identifyQueue?.add(() => cluster.identifyShard(shardId));
570
+ }
571
+ #getShardIdsForCluster(clusterId) {
572
+ let start = clusterId * this.options.shardsPerCluster, end = Math.min(start + this.options.shardsPerCluster, this.totalShards);
573
+ return Array.from({ length: end - start }, (_, i) => start + i);
574
+ }
575
+ async #spawnCluster(id) {
576
+ let shardIds = this.#getShardIdsForCluster(id), firstShardId = shardIds[0], lastShardId = shardIds[shardIds.length - 1];
577
+ this.emit("debug", `Spawning cluster ${id} (shards ${firstShardId}-${lastShardId})`);
578
+ let env = {
579
+ ...process.env,
580
+ BAKIT_CLUSTER_ID: String(id),
581
+ BAKIT_CLUSTER_SHARD_TOTAL: String(this.totalShards),
582
+ BAKIT_CLUSTER_SHARD_LIST: JSON.stringify(shardIds),
583
+ BAKIT_DISCORD_TOKEN: this.options.token,
584
+ BAKIT_DISCORD_INTENTS: String(this.options.intents),
585
+ BAKIT_DISCORD_GATEWAY_URL: this.#gatewayInfo?.url,
586
+ BAKIT_DISCORD_GATEWAY_VERSION: "10"
587
+ }, cluster = new ClusterProcess(this, id, { env });
588
+ this.#bindClusterEvents(cluster, id), this.clusters.set(id, cluster), this.emit("clusterCreate", cluster);
589
+ }
590
+ #bindClusterEvents(cluster, id) {
591
+ cluster.on("ready", () => {
592
+ this.#readyCount++, this.emit("clusterReady", cluster), this.ready && this.emit("ready");
593
+ }), cluster.process.on("exit", (code) => {
594
+ this.emit("clusterExit", cluster, code), this.clusters.delete(id), this.#readyCount = Math.max(0, this.#readyCount - 1);
595
+ }), cluster.on("error", (err) => {
596
+ this.emit("clusterError", cluster, err);
597
+ }), cluster.on("debug", (msg) => {
598
+ this.emit("debug", `[Cluster ${id}] ${msg}`);
599
+ }), cluster.on("dispatch", (shardId, payload) => {
600
+ this.emit("dispatch", cluster, shardId, payload);
601
+ }), cluster.on("raw", (shardId, payload) => {
602
+ this.emit("raw", cluster, shardId, payload);
603
+ }), cluster.on("shardAdd", (shardId) => {
604
+ this.emit("shardAdd", cluster, shardId);
605
+ }), cluster.on("shardReady", (shardId) => {
606
+ this.emit("shardReady", cluster, shardId);
607
+ }), cluster.on("shardDisconnect", (shardId, code) => {
608
+ this.emit("shardDisconnect", cluster, shardId, code);
609
+ }), cluster.on("shardResume", (shardId) => {
610
+ this.emit("shardResume", cluster, shardId);
611
+ }), cluster.on("shardError", (shardId, error) => {
612
+ this.emit("shardError", cluster, shardId, error);
613
+ }), cluster.on("needIdentify", (shardId) => {
614
+ this.requestIdentify(cluster, shardId);
615
+ });
616
+ }
617
+ };
485
618
 
486
- export { DEFAULT_GATEWAY_MANAGER_OPTIONS, DEFAULT_WORKER_PATH, GatewayWorkerState, ShardState, ShardStrategy, bindWorkerToProcess, createGatewayManager, createShard, createWorker, getWorkerOptions };
619
+ export { Cluster, ClusterProcess, Shard, ShardState, ShardStrategy, ShardingManager };