seyfert 3.1.3-dev-15068780757.0 → 3.1.3-dev-15124346914.0

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.
@@ -1,14 +1,15 @@
1
- import { type DeepPartial, type MakeRequired, type When } from '../common';
1
+ import { type Awaitable, type DeepPartial, type MakeRequired, type When } from '../common';
2
2
  import { EventHandler } from '../events';
3
3
  import type { GatewayDispatchPayload } from '../types';
4
4
  import { Shard, type ShardManagerOptions, type WorkerData } from '../websocket';
5
- import type { WorkerMessages, WorkerShardInfo } from '../websocket/discord/worker';
5
+ import type { ClientHeartbeaterMessages, WorkerMessages, WorkerShardInfo } from '../websocket/discord/worker';
6
6
  import type { ManagerMessages, ManagerSpawnShards } from '../websocket/discord/workermanager';
7
7
  import type { BaseClientOptions, ServicesOptions, StartOptions } from './base';
8
8
  import { BaseClient } from './base';
9
9
  import type { Client, ClientOptions } from './client';
10
10
  import { MemberUpdateHandler } from '../websocket/discord/events/memberUpdate';
11
11
  import { PresenceUpdateHandler } from '../websocket/discord/events/presenceUpdate';
12
+ import type { WorkerHeartbeaterMessages } from '../websocket/discord/heartbeater';
12
13
  import type { ShardData } from '../websocket/discord/shared';
13
14
  import { Collectors } from './collectors';
14
15
  import { type ClientUserStructure } from './transformers';
@@ -35,8 +36,8 @@ export declare class WorkerClient<Ready extends boolean = boolean> extends BaseC
35
36
  get workerData(): WorkerData;
36
37
  start(options?: Omit<DeepPartial<StartOptions>, 'httpConnection' | 'token' | 'connection'>): Promise<void>;
37
38
  loadEvents(dir?: string): Promise<void>;
38
- postMessage(body: WorkerMessages): unknown;
39
- handleManagerMessages(data: ManagerMessages): Promise<any>;
39
+ postMessage(body: WorkerMessages | ClientHeartbeaterMessages): unknown;
40
+ handleManagerMessages(data: ManagerMessages | WorkerHeartbeaterMessages): Promise<any>;
40
41
  calculateShardId(guildId: string): number;
41
42
  private generateNonce;
42
43
  private generateSendPromise;
@@ -51,8 +52,8 @@ export interface WorkerClientOptions extends BaseClientOptions {
51
52
  commands?: NonNullable<Client['options']>['commands'];
52
53
  handlePayload?: ShardManagerOptions['handlePayload'];
53
54
  gateway?: ClientOptions['gateway'];
54
- postMessage?: (body: unknown) => unknown;
55
+ postMessage?: (body: unknown) => Awaitable<unknown>;
55
56
  /** can have perfomance issues in big bots if the client sends every event, specially in startup (false by default) */
56
57
  sendPayloadToParent?: boolean;
57
- handleManagerMessages?(message: ManagerMessages): any;
58
+ handleManagerMessages?(message: ManagerMessages | WorkerHeartbeaterMessages): Awaitable<unknown>;
58
59
  }
@@ -131,6 +131,12 @@ class WorkerClient extends base_1.BaseClient {
131
131
  }
132
132
  async handleManagerMessages(data) {
133
133
  switch (data.type) {
134
+ case 'HEARTBEAT':
135
+ this.postMessage({
136
+ type: 'ACK_HEARTBEAT',
137
+ workerId: workerData.workerId,
138
+ });
139
+ break;
134
140
  case 'CACHE_RESULT':
135
141
  if (this.cache.adapter instanceof cache_1.WorkerAdapter && this.cache.adapter.promises.has(data.nonce)) {
136
142
  const cacheData = this.cache.adapter.promises.get(data.nonce);
@@ -0,0 +1,17 @@
1
+ import type { Awaitable } from '../../common';
2
+ export type WorkerHeartbeaterMessages = SendHeartbeat;
3
+ export type CreateHeartbeaterMessage<T extends string, D extends object = object> = {
4
+ type: T;
5
+ } & D;
6
+ export type SendHeartbeat = CreateHeartbeaterMessage<'HEARTBEAT'>;
7
+ export declare class Heartbeater {
8
+ sendMethod: (workerId: number, data: WorkerHeartbeaterMessages) => Awaitable<void>;
9
+ interval: number;
10
+ store: Map<number, {
11
+ ack: boolean;
12
+ interval: NodeJS.Timeout;
13
+ }>;
14
+ constructor(sendMethod: (workerId: number, data: WorkerHeartbeaterMessages) => Awaitable<void>, interval: number);
15
+ register(workerId: number, recreate: (workerId: number) => Awaitable<void>): void;
16
+ acknowledge(workerId: number): void;
17
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Heartbeater = void 0;
4
+ class Heartbeater {
5
+ sendMethod;
6
+ interval;
7
+ store = new Map();
8
+ constructor(sendMethod, interval) {
9
+ this.sendMethod = sendMethod;
10
+ this.interval = interval;
11
+ }
12
+ register(workerId, recreate) {
13
+ if (this.interval <= 0)
14
+ return;
15
+ this.store.set(workerId, {
16
+ ack: true,
17
+ interval: setInterval(() => {
18
+ const heartbeat = this.store.get(workerId);
19
+ if (!heartbeat.ack) {
20
+ heartbeat.ack = true;
21
+ return recreate(workerId);
22
+ }
23
+ heartbeat.ack = false;
24
+ this.sendMethod(workerId, { type: 'HEARTBEAT' });
25
+ }, this.interval),
26
+ });
27
+ }
28
+ acknowledge(workerId) {
29
+ const heartbeat = this.store.get(workerId);
30
+ if (!heartbeat)
31
+ return;
32
+ heartbeat.ack = true;
33
+ }
34
+ }
35
+ exports.Heartbeater = Heartbeater;
@@ -23,7 +23,7 @@ export declare class Shard {
23
23
  heart: ShardHeart;
24
24
  bucket: DynamicBucket;
25
25
  offlineSendQueue: ((_?: unknown) => void)[];
26
- pendingGuilds: Set<string>;
26
+ pendingGuilds?: Set<string>;
27
27
  options: MakeRequired<ShardOptions, 'properties' | 'ratelimitOptions' | 'reconnectTimeout' | 'connectionTimeout'>;
28
28
  isReady: boolean;
29
29
  connectionTimeout?: NodeJS.Timeout;
@@ -24,7 +24,7 @@ class Shard {
24
24
  };
25
25
  bucket;
26
26
  offlineSendQueue = [];
27
- pendingGuilds = new Set();
27
+ pendingGuilds;
28
28
  options;
29
29
  isReady = false;
30
30
  connectionTimeout;
@@ -217,15 +217,13 @@ class Shard {
217
217
  clearTimeout(this.connectionTimeout);
218
218
  this.connectionTimeout = undefined;
219
219
  if ((0, common_1.hasIntent)(this.options.intents, 'Guilds')) {
220
- for (let i = 0; i < packet.d.guilds.length; i++) {
221
- this.pendingGuilds.add(packet.d.guilds.at(i).id);
222
- }
220
+ this.pendingGuilds = new Set(...packet.d.guilds.map(guild => guild.id));
223
221
  }
224
222
  this.data.resume_gateway_url = packet.d.resume_gateway_url;
225
223
  this.data.session_id = packet.d.session_id;
226
224
  this.offlineSendQueue.map(resolve => resolve());
227
225
  this.options.handlePayload(this.id, packet);
228
- if (this.pendingGuilds.size === 0) {
226
+ if (this.pendingGuilds?.size === 0) {
229
227
  this.isReady = true;
230
228
  this.options.handlePayload(this.id, {
231
229
  t: types_1.GatewayDispatchEvents.GuildsReady,
@@ -237,7 +235,7 @@ class Shard {
237
235
  }
238
236
  case types_1.GatewayDispatchEvents.GuildCreate:
239
237
  case types_1.GatewayDispatchEvents.GuildDelete:
240
- if (this.pendingGuilds.delete(packet.d.id)) {
238
+ if (this.pendingGuilds?.delete(packet.d.id)) {
241
239
  packet.t = `RAW_${packet.t}`;
242
240
  this.options.handlePayload(this.id, packet);
243
241
  if (this.pendingGuilds.size === 0) {
@@ -55,6 +55,8 @@ export interface WorkerManagerOptions extends Omit<ShardManagerOptions, 'handleP
55
55
  */
56
56
  shardsPerWorker?: number;
57
57
  workerProxy?: boolean;
58
+ /** @default 15000 */
59
+ heartbeaterInterval?: number;
58
60
  path: string;
59
61
  handlePayload?(shardId: number, workerId: number, packet: GatewayDispatchPayload): any;
60
62
  handleWorkerMessage?(message: WorkerMessages): any;
@@ -68,7 +68,9 @@ export type CustomWorkerClientMessages = {
68
68
  workerId: number;
69
69
  } & Identify<CustomWorkerClientEvents[K] extends never ? {} : CustomWorkerClientEvents[K]>>;
70
70
  };
71
- export type WorkerMessages = {
71
+ export type ClientHeartbeaterMessages = ACKHeartbeat;
72
+ export type ACKHeartbeat = CreateWorkerMessage<'ACK_HEARTBEAT'>;
73
+ export type WorkerMessages = ClientHeartbeaterMessages | {
72
74
  [K in BaseWorkerMessage['type']]: Identify<Extract<BaseWorkerMessage, {
73
75
  type: K;
74
76
  }>>;
@@ -5,6 +5,7 @@ import { type Adapter } from '../../cache';
5
5
  import { type Identify, type PickPartial } from '../../common';
6
6
  import type { GatewayPresenceUpdateData, GatewaySendPayload } from '../../types';
7
7
  import { ConnectQueue } from '../structures/timeout';
8
+ import { Heartbeater, type WorkerHeartbeaterMessages } from './heartbeater';
8
9
  import type { ShardOptions, WorkerData, WorkerManagerOptions } from './shared';
9
10
  import type { WorkerInfo, WorkerMessages, WorkerShardInfo } from './worker';
10
11
  export declare class WorkerManager extends Map<number, (ClusterWorker | WorkerThreadsWorker | {
@@ -31,6 +32,7 @@ export declare class WorkerManager extends Map<number, (ClusterWorker | WorkerTh
31
32
  rest: ApiHandler;
32
33
  reshardingWorkerQueue: (() => void)[];
33
34
  private _info?;
35
+ heartbeater: Heartbeater;
34
36
  constructor(options: Omit<PickPartial<WorkerManagerOptions, 'token' | 'intents' | 'info' | 'handlePayload' | 'handleWorkerMessage'>, 'resharding'> & {
35
37
  resharding?: PickPartial<NonNullable<WorkerManagerOptions['resharding']>, 'getInfo'>;
36
38
  });
@@ -52,8 +54,8 @@ export declare class WorkerManager extends Map<number, (ClusterWorker | WorkerTh
52
54
  }): Promise<number>;
53
55
  calculateShardId(guildId: string): number;
54
56
  calculateWorkerId(shardId: number): number;
55
- postMessage(id: number, body: ManagerMessages): void;
56
- prepareWorkers(shards: number[][], resharding?: boolean): void;
57
+ postMessage(id: number, body: ManagerMessages | WorkerHeartbeaterMessages): void;
58
+ prepareWorkers(shards: number[][], rawResharding?: boolean): void;
57
59
  createWorker(workerData: WorkerData): ClusterWorker | WorkerThreadsWorker | ({
58
60
  ready?: boolean;
59
61
  } & {
@@ -13,6 +13,7 @@ const common_1 = require("../../common");
13
13
  const constants_1 = require("../constants");
14
14
  const structures_1 = require("../structures");
15
15
  const timeout_1 = require("../structures/timeout");
16
+ const heartbeater_1 = require("./heartbeater");
16
17
  class WorkerManager extends Map {
17
18
  static prepareSpaces(options, logger) {
18
19
  logger?.info('Preparing buckets');
@@ -35,6 +36,7 @@ class WorkerManager extends Map {
35
36
  rest;
36
37
  reshardingWorkerQueue = [];
37
38
  _info;
39
+ heartbeater;
38
40
  constructor(options) {
39
41
  super();
40
42
  this.options = options;
@@ -46,6 +48,7 @@ class WorkerManager extends Map {
46
48
  return oldFn(message);
47
49
  };
48
50
  }
51
+ this.heartbeater = new heartbeater_1.Heartbeater(this.postMessage.bind(this), options.heartbeaterInterval ?? 15e3);
49
52
  }
50
53
  setCache(adapter) {
51
54
  this.cacheAdapter = adapter;
@@ -101,7 +104,8 @@ class WorkerManager extends Map {
101
104
  return this.debugger?.error(`Worker ${id} does not exists.`);
102
105
  switch (this.options.mode) {
103
106
  case 'clusters':
104
- worker.send(body);
107
+ if (worker.isConnected())
108
+ worker.send(body);
105
109
  break;
106
110
  case 'threads':
107
111
  worker.postMessage(body);
@@ -111,33 +115,40 @@ class WorkerManager extends Map {
111
115
  break;
112
116
  }
113
117
  }
114
- prepareWorkers(shards, resharding = false) {
118
+ prepareWorkers(shards, rawResharding = false) {
115
119
  const worker_threads = (0, common_1.lazyLoadPackage)('node:worker_threads');
116
120
  if (!worker_threads)
117
121
  throw new Error('Cannot prepare workers without worker_threads.');
118
122
  for (let i = 0; i < shards.length; i++) {
123
+ const registerWorker = (resharding) => {
124
+ const worker = this.createWorker({
125
+ path: this.options.path,
126
+ debug: this.options.debug,
127
+ token: this.options.token,
128
+ shards: shards[i],
129
+ intents: this.options.intents,
130
+ workerId: i,
131
+ workerProxy: this.options.workerProxy,
132
+ totalShards: resharding ? this._info.shards : this.totalShards,
133
+ mode: this.options.mode,
134
+ resharding,
135
+ totalWorkers: shards.length,
136
+ info: {
137
+ ...this.options.info,
138
+ shards: this.totalShards,
139
+ },
140
+ compress: this.options.compress,
141
+ });
142
+ this.set(i, worker);
143
+ };
119
144
  const workerExists = this.has(i);
120
- if (resharding || !workerExists) {
121
- this[resharding ? 'reshardingWorkerQueue' : 'workerQueue'].push(() => {
122
- const worker = this.createWorker({
123
- path: this.options.path,
124
- debug: this.options.debug,
125
- token: this.options.token,
126
- shards: shards[i],
127
- intents: this.options.intents,
128
- workerId: i,
129
- workerProxy: this.options.workerProxy,
130
- totalShards: resharding ? this._info.shards : this.totalShards,
131
- mode: this.options.mode,
132
- resharding,
133
- totalWorkers: shards.length,
134
- info: {
135
- ...this.options.info,
136
- shards: this.totalShards,
137
- },
138
- compress: this.options.compress,
145
+ if (rawResharding || !workerExists) {
146
+ this[rawResharding ? 'reshardingWorkerQueue' : 'workerQueue'].push(() => {
147
+ registerWorker(rawResharding);
148
+ this.heartbeater.register(i, () => {
149
+ this.delete(i);
150
+ registerWorker(false);
139
151
  });
140
- this.set(i, worker);
141
152
  });
142
153
  }
143
154
  }
@@ -170,6 +181,9 @@ class WorkerManager extends Map {
170
181
  env,
171
182
  });
172
183
  worker.on('message', data => this.handleWorkerMessage(data));
184
+ worker.on('error', err => {
185
+ this.debugger?.error(`[Worker #${workerData.workerId}]`, err);
186
+ });
173
187
  return worker;
174
188
  }
175
189
  case 'clusters': {
@@ -204,6 +218,9 @@ class WorkerManager extends Map {
204
218
  }
205
219
  async handleWorkerMessage(message) {
206
220
  switch (message.type) {
221
+ case 'ACK_HEARTBEAT':
222
+ this.heartbeater.acknowledge(message.workerId);
223
+ break;
207
224
  case 'WORKER_READY_RESHARDING':
208
225
  {
209
226
  this.get(message.workerId).resharded = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seyfert",
3
- "version": "3.1.3-dev-15068780757.0",
3
+ "version": "3.1.3-dev-15124346914.0",
4
4
  "description": "The most advanced framework for discord bots",
5
5
  "main": "./lib/index.js",
6
6
  "module": "./lib/index.js",