seyfert 5.1.0 → 5.1.1-dev-34481657717.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.
@@ -30,6 +30,7 @@ exports.installPluginMiddlewares = installPluginMiddlewares;
30
30
  exports.resolveGatewayIntent = resolveGatewayIntent;
31
31
  exports.unknownGatewayIntentBits = unknownGatewayIntentBits;
32
32
  exports.validatePluginRequirements = validatePluginRequirements;
33
+ const utils_1 = require("../../common/it/utils");
33
34
  const types_1 = require("../../types");
34
35
  const api_1 = require("./api");
35
36
  const errors_1 = require("./errors");
@@ -105,7 +106,7 @@ function createPluginRuntimeRegistry(plugins = []) {
105
106
  function runPluginRegister(record, registry) {
106
107
  try {
107
108
  const result = record.plugin.register?.((0, api_1.createPluginApi)(record, registry));
108
- if (isPromiseLike(result)) {
109
+ if ((0, utils_1.isPromiseLike)(result)) {
109
110
  Promise.resolve(result).catch(() => undefined);
110
111
  throw (0, errors_1.createPluginConflictError)(record.plugin.name, 'register', record.index, 'register(api) must be synchronous.', record.plugin.instanceId);
111
112
  }
@@ -860,9 +861,3 @@ const reservedContextKeys = new Set([
860
861
  function capitalize(value) {
861
862
  return value[0]?.toUpperCase() + value.slice(1);
862
863
  }
863
- function isPromiseLike(value) {
864
- return (typeof value === 'object' &&
865
- value !== null &&
866
- 'then' in value &&
867
- typeof value.then === 'function');
868
- }
@@ -39,7 +39,7 @@ export declare class WorkerClient<Ready extends boolean = boolean> extends BaseC
39
39
  get workerData(): WorkerData;
40
40
  start(options?: Omit<DeepPartial<StartOptions>, 'httpConnection' | 'token' | 'connection'>): Promise<void>;
41
41
  loadEvents(dir?: string): Promise<void>;
42
- postMessage(body: WorkerMessages | ClientHeartbeaterMessages): unknown;
42
+ postMessage(body: WorkerMessages | ClientHeartbeaterMessages): Promise<unknown>;
43
43
  handleManagerMessages(data: ManagerMessages | WorkerHeartbeaterMessages): Promise<any>;
44
44
  calculateShardId(guildId: string): number;
45
45
  /**
@@ -51,6 +51,7 @@ export declare class WorkerClient<Ready extends boolean = boolean> extends BaseC
51
51
  private sendGatewayPayloadOnShard;
52
52
  private generateNonce;
53
53
  private generateSendPromise;
54
+ private sendRequest;
54
55
  tellWorker<R, V extends Record<string, unknown>>(workerId: number, func: (_: this, vars: V) => R, vars: V): Promise<R>;
55
56
  tellWorkers<R, V extends Record<string, unknown>>(func: (_: this, vars: V) => R, vars: V): Promise<Awaited<R>[]>;
56
57
  createShard(id: number, data: Pick<ManagerSpawnShards, 'info' | 'compress' | 'properties'>): Shard;
@@ -49,9 +49,6 @@ class WorkerClient extends base_1.BaseClient {
49
49
  resharding = new Map();
50
50
  constructor(options) {
51
51
  super(options);
52
- if (options?.postMessage) {
53
- this.postMessage = options.postMessage;
54
- }
55
52
  if (this.options.handleManagerMessages) {
56
53
  const oldFn = this.handleManagerMessages.bind(this);
57
54
  this.handleManagerMessages = async (message) => {
@@ -102,7 +99,11 @@ class WorkerClient extends base_1.BaseClient {
102
99
  manager = worker_threads?.parentPort;
103
100
  }
104
101
  if (workerData.mode !== 'custom')
105
- (manager ?? process).on('message', (data) => this.handleManagerMessages(data));
102
+ (manager ?? process).on('message', (data) => {
103
+ void this.handleManagerMessages(data).catch(error => {
104
+ this.logger.error('Worker manager message handling failed', error);
105
+ });
106
+ });
106
107
  this.configureLogger({ name: `[Worker #${workerData.workerId}]` }, this.options.logger);
107
108
  if (workerData.debug) {
108
109
  this.debugger = new __1.Logger({
@@ -123,7 +124,7 @@ class WorkerClient extends base_1.BaseClient {
123
124
  this.rest.workerData = workerData;
124
125
  await super.start(options);
125
126
  workerData.intents = this.resolvePluginGatewayIntents(workerData.intents);
126
- this.postMessage({
127
+ await this.postMessage({
127
128
  type: workerData.resharding ? 'WORKER_START_RESHARDING' : 'WORKER_START',
128
129
  workerId: workerData.workerId,
129
130
  });
@@ -138,15 +139,24 @@ class WorkerClient extends base_1.BaseClient {
138
139
  }
139
140
  await (0, plugins_1.runPluginHooks)(this, 'events:afterLoad', this, dir);
140
141
  }
141
- postMessage(body) {
142
+ async postMessage(body) {
143
+ if (this.options.postMessage)
144
+ return this.options.postMessage.call(this, body);
142
145
  if (manager)
143
146
  return manager.postMessage(body);
144
- return process.send(body);
147
+ return new Promise((resolve, reject) => {
148
+ process.send(body, error => {
149
+ if (error)
150
+ reject(error);
151
+ else
152
+ resolve();
153
+ });
154
+ });
145
155
  }
146
156
  async handleManagerMessages(data) {
147
157
  switch (data.type) {
148
158
  case 'HEARTBEAT':
149
- this.postMessage({
159
+ await this.postMessage({
150
160
  type: 'ACK_HEARTBEAT',
151
161
  workerId: workerData.workerId,
152
162
  });
@@ -170,7 +180,7 @@ class WorkerClient extends base_1.BaseClient {
170
180
  this.logger.fatal(`Worker trying to send payload by non-existent shard (#${data.shardId})`);
171
181
  return;
172
182
  }
173
- this.postMessage({
183
+ await this.postMessage({
174
184
  type: 'RESULT_PAYLOAD',
175
185
  nonce: data.nonce,
176
186
  workerId: this.workerId,
@@ -225,15 +235,19 @@ class WorkerClient extends base_1.BaseClient {
225
235
  if (payload.t !== types_1.GatewayDispatchEvents.GuildsReady)
226
236
  return;
227
237
  if (++shardsConnected === workerData.shards.length) {
228
- self.postMessage({
238
+ void self
239
+ .postMessage({
229
240
  type: 'WORKER_READY_RESHARDING',
230
241
  workerId: workerData.workerId,
242
+ })
243
+ .catch(error => {
244
+ self.logger.error('Failed to send WORKER_READY_RESHARDING to the worker manager', error);
231
245
  });
232
246
  }
233
247
  },
234
248
  });
235
249
  this.resharding.set(id, shard);
236
- this.postMessage({
250
+ await this.postMessage({
237
251
  type: 'CONNECT_QUEUE_RESHARDING',
238
252
  shardId: id,
239
253
  workerId: workerData.workerId,
@@ -251,7 +265,7 @@ class WorkerClient extends base_1.BaseClient {
251
265
  }
252
266
  const shard = this.createShard(id, data);
253
267
  this.shards.set(id, shard);
254
- this.postMessage({
268
+ await this.postMessage({
255
269
  type: 'CONNECT_QUEUE',
256
270
  shardId: id,
257
271
  workerId: workerData.workerId,
@@ -266,7 +280,7 @@ class WorkerClient extends base_1.BaseClient {
266
280
  this.logger.fatal(`Worker trying to get non-existent shard (#${data.shardId})`);
267
281
  return;
268
282
  }
269
- this.postMessage({
283
+ await this.postMessage({
270
284
  ...generateShardInfo(shard),
271
285
  nonce: data.nonce,
272
286
  type: 'SHARD_INFO',
@@ -276,7 +290,7 @@ class WorkerClient extends base_1.BaseClient {
276
290
  break;
277
291
  case 'WORKER_INFO':
278
292
  {
279
- this.postMessage({
293
+ await this.postMessage({
280
294
  shards: [...this.shards.values()].map(generateShardInfo),
281
295
  workerId: workerData.workerId,
282
296
  type: 'WORKER_INFO',
@@ -310,7 +324,7 @@ class WorkerClient extends base_1.BaseClient {
310
324
  catch (e) {
311
325
  result = e;
312
326
  }
313
- this.postMessage({
327
+ await this.postMessage({
314
328
  type: 'EVAL_RESPONSE',
315
329
  response: result,
316
330
  workerId: workerData.workerId,
@@ -330,7 +344,7 @@ class WorkerClient extends base_1.BaseClient {
330
344
  break;
331
345
  case 'WORKER_ALREADY_EXISTS_RESHARDING':
332
346
  {
333
- this.postMessage({
347
+ await this.postMessage({
334
348
  type: 'WORKER_START_RESHARDING',
335
349
  workerId: workerData.workerId,
336
350
  });
@@ -341,7 +355,7 @@ class WorkerClient extends base_1.BaseClient {
341
355
  for (const i of this.shards.values()) {
342
356
  await i.disconnect(websocket_1.ShardSocketCloseCodes.Resharding);
343
357
  }
344
- this.postMessage({
358
+ await this.postMessage({
345
359
  type: 'DISCONNECTED_ALL_SHARDS_RESHARDING',
346
360
  workerId: workerData.workerId,
347
361
  });
@@ -407,17 +421,31 @@ class WorkerClient extends base_1.BaseClient {
407
421
  this.promises.set(nonce, { resolve: res, timeout });
408
422
  });
409
423
  }
410
- tellWorker(workerId, func, vars) {
424
+ async sendRequest(nonce, operation, send) {
425
+ const response = this.generateSendPromise(nonce, operation);
426
+ try {
427
+ const [result] = await Promise.all([response, send()]);
428
+ return result;
429
+ }
430
+ catch (error) {
431
+ const pending = this.promises.get(nonce);
432
+ if (pending) {
433
+ this.promises.delete(nonce);
434
+ clearTimeout(pending.timeout);
435
+ }
436
+ throw error;
437
+ }
438
+ }
439
+ async tellWorker(workerId, func, vars) {
411
440
  const nonce = this.generateNonce();
412
- this.postMessage({
441
+ return this.sendRequest(nonce, 'Worker request', () => this.postMessage({
413
442
  type: 'EVAL_TO_WORKER',
414
443
  func: func.toString(),
415
444
  toWorkerId: workerId,
416
445
  workerId: workerData.workerId,
417
446
  nonce,
418
447
  vars: JSON.stringify(vars),
419
- });
420
- return this.generateSendPromise(nonce);
448
+ }));
421
449
  }
422
450
  tellWorkers(func, vars) {
423
451
  const promises = [];
@@ -446,13 +474,18 @@ class WorkerClient extends base_1.BaseClient {
446
474
  async handlePayload(shardId, payload) {
447
475
  await handlePayload?.(shardId, payload);
448
476
  const pluginPacket = await onPacket(payload, shardId);
449
- if (self.options.sendPayloadToParent && pluginPacket !== null)
450
- self.postMessage({
477
+ if (self.options.sendPayloadToParent && pluginPacket !== null) {
478
+ void self
479
+ .postMessage({
451
480
  workerId: workerData.workerId,
452
481
  shardId,
453
482
  type: 'RECEIVE_PAYLOAD',
454
483
  payload: pluginPacket,
484
+ })
485
+ .catch(error => {
486
+ self.logger.error('Failed to send RECEIVE_PAYLOAD to the worker manager', error);
455
487
  });
488
+ }
456
489
  },
457
490
  });
458
491
  return shard;
@@ -522,9 +555,11 @@ class WorkerClient extends base_1.BaseClient {
522
555
  this.applicationId = packet.d.application.id;
523
556
  this.me = transformers_1.Transformers.ClientUser(this, packet.d.user, packet.d.application);
524
557
  if ([...this.shards.values()].every(shard => shard.data.session_id)) {
525
- this.postMessage({
558
+ void this.postMessage({
526
559
  type: 'WORKER_SHARDS_CONNECTED',
527
560
  workerId: this.workerId,
561
+ }).catch(error => {
562
+ this.logger.error('Failed to send WORKER_SHARDS_CONNECTED to the worker manager', error);
528
563
  });
529
564
  await this.events.runEvent('WORKER_SHARDS_CONNECTED', this, this.me, -1);
530
565
  }
@@ -535,9 +570,11 @@ class WorkerClient extends base_1.BaseClient {
535
570
  case types_1.GatewayDispatchEvents.GuildsReady:
536
571
  {
537
572
  if ([...this.shards.values()].every(shard => shard.isReady)) {
538
- this.postMessage({
573
+ void this.postMessage({
539
574
  type: 'WORKER_READY',
540
575
  workerId: this.workerId,
576
+ }).catch(error => {
577
+ this.logger.error('Failed to send WORKER_READY to the worker manager', error);
541
578
  });
542
579
  await this.events.runEvent('WORKER_READY', this, this.me, -1);
543
580
  }
@@ -1,7 +1,9 @@
1
1
  import type { Cache } from '../../cache';
2
2
  import { type APIPartialEmoji, GatewayIntentBits } from '../../types';
3
+ import type { ColorResolvable, EmojiResolvable } from '../types/resolvables';
4
+ import type { ObjectToLower, ObjectToSnake, TypeArray } from '../types/util';
5
+ import type { Logger } from './logger';
3
6
  export { type FakePromiseResult, fakePromise } from './fake-promise';
4
- import { type ColorResolvable, type EmojiResolvable, type Logger, type ObjectToLower, type ObjectToSnake, type TypeArray } from '..';
5
7
  /**
6
8
  * Calculates the shard ID for a guild based on its ID.
7
9
  * @param guildId The ID of the guild.
@@ -28,6 +30,7 @@ export declare function delay<T>(time: number, result?: T): Promise<T>;
28
30
  * @returns `true` if the value is an object, otherwise `false`.
29
31
  */
30
32
  export declare function isObject(o: any): o is Record<string, unknown>;
33
+ export declare function isPromiseLike<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
31
34
  /**
32
35
  * Merges multiple options objects together, deeply extending objects.
33
36
  * @param defaults The default options object.
@@ -5,6 +5,7 @@ exports.calculateShardId = calculateShardId;
5
5
  exports.resolveColor = resolveColor;
6
6
  exports.delay = delay;
7
7
  exports.isObject = isObject;
8
+ exports.isPromiseLike = isPromiseLike;
8
9
  exports.MergeOptions = MergeOptions;
9
10
  exports.filterSplit = filterSplit;
10
11
  exports.toSnakeCase = toSnakeCase;
@@ -24,9 +25,10 @@ exports.assertString = assertString;
24
25
  const node_fs_1 = require("node:fs");
25
26
  const node_path_1 = require("node:path");
26
27
  const types_1 = require("../../types");
28
+ const constants_1 = require("./constants");
29
+ const error_1 = require("./error");
27
30
  var fake_promise_1 = require("./fake-promise");
28
31
  Object.defineProperty(exports, "fakePromise", { enumerable: true, get: function () { return fake_promise_1.fakePromise; } });
29
- const __1 = require("..");
30
32
  /**
31
33
  * Calculates the shard ID for a guild based on its ID.
32
34
  * @param guildId The ID of the guild.
@@ -39,7 +41,7 @@ function calculateShardId(guildId, shards) {
39
41
  // It is used for resolve color for better performance
40
42
  const ColorLookup = {
41
43
  Random: -1, // Special value for random color
42
- ...__1.EmbedColors,
44
+ ...constants_1.EmbedColors,
43
45
  };
44
46
  /**
45
47
  * Resolves the color to a numeric representation.
@@ -50,7 +52,7 @@ function resolveColor(color) {
50
52
  const type = typeof color;
51
53
  if (type === 'number') {
52
54
  if (!Number.isInteger(color) || color < 0)
53
- throw new __1.SeyfertError('INTERNAL_ERROR', { metadata: { detail: `Invalid color: ${color}` } });
55
+ throw new error_1.SeyfertError('INTERNAL_ERROR', { metadata: { detail: `Invalid color: ${color}` } });
54
56
  return color;
55
57
  }
56
58
  if (type === 'string') {
@@ -61,14 +63,14 @@ function resolveColor(color) {
61
63
  if (color.startsWith('#')) {
62
64
  const hex = color.slice(1);
63
65
  if (!/^[\da-f]+$/i.test(hex))
64
- throw new __1.SeyfertError('INTERNAL_ERROR', { metadata: { detail: `Invalid color: ${color}` } });
66
+ throw new error_1.SeyfertError('INTERNAL_ERROR', { metadata: { detail: `Invalid color: ${color}` } });
65
67
  return Number.parseInt(hex, 16);
66
68
  }
67
- return __1.EmbedColors.Default;
69
+ return constants_1.EmbedColors.Default;
68
70
  }
69
71
  return Array.isArray(color) && color.length >= 3
70
72
  ? (color[0] << 16) | (color[1] << 8) | color[2]
71
- : __1.EmbedColors.Default;
73
+ : constants_1.EmbedColors.Default;
72
74
  }
73
75
  /**
74
76
  * Delays the resolution of a Promise by the specified time.
@@ -87,6 +89,10 @@ function delay(time, result) {
87
89
  function isObject(o) {
88
90
  return o && typeof o === 'object' && !Array.isArray(o);
89
91
  }
92
+ function isPromiseLike(value) {
93
+ return (((typeof value === 'object' && value !== null) || typeof value === 'function') &&
94
+ typeof value.then === 'function');
95
+ }
90
96
  function isPlainObject(o) {
91
97
  if (!o || typeof o !== 'object')
92
98
  return false;
@@ -301,7 +307,7 @@ function isCloudflareWorker() {
301
307
  * @returns The unix millisecond timestamp.
302
308
  */
303
309
  function snowflakeToTimestamp(id) {
304
- return Number((BigInt(id) >> 22n) + __1.DiscordEpoch);
310
+ return Number((BigInt(id) >> 22n) + constants_1.DiscordEpoch);
305
311
  }
306
312
  function resolvePartialEmoji(emoji) {
307
313
  if (typeof emoji === 'string') {
@@ -383,6 +389,6 @@ function toBuffer(arrayBuffer) {
383
389
  }
384
390
  function assertString(value, message) {
385
391
  if (!(typeof value === 'string' && value !== '')) {
386
- throw new __1.SeyfertError('INTERNAL_ERROR', { metadata: { detail: message ?? 'Value is not a string' } });
392
+ throw new error_1.SeyfertError('INTERNAL_ERROR', { metadata: { detail: message ?? 'Value is not a string' } });
387
393
  }
388
394
  }
@@ -4,9 +4,6 @@ exports.LangsHandler = void 0;
4
4
  const node_path_1 = require("node:path");
5
5
  const common_1 = require("../common");
6
6
  const router_1 = require("./router");
7
- function isPromiseLike(value) {
8
- return !!value && typeof value.then === 'function';
9
- }
10
7
  class LangsHandler extends common_1.BaseHandler {
11
8
  values = {};
12
9
  __paths = {};
@@ -46,7 +43,7 @@ class LangsHandler extends common_1.BaseHandler {
46
43
  parse(file) {
47
44
  const oldLocale = file.name.split('.').slice(0, -1).join('.') || file.name;
48
45
  const result = this.onFile(oldLocale, file);
49
- if (isPromiseLike(result))
46
+ if ((0, common_1.isPromiseLike)(result))
50
47
  return result.then(value => this.applyParsedFile(file, value));
51
48
  this.applyParsedFile(file, result);
52
49
  return;
@@ -1,8 +1,8 @@
1
- import cluster, { type Worker as ClusterWorker } from 'node:cluster';
1
+ import { type Worker as ClusterWorker } from 'node:cluster';
2
2
  import type { Worker as WorkerThreadsWorker } from 'node:worker_threads';
3
3
  import { ApiHandler, type CustomWorkerManagerEvents, Logger, type UsingClient, type WorkerClient } from '../..';
4
4
  import { type Adapter } from '../../cache';
5
- import { type Identify, type PickPartial } from '../../common';
5
+ import { type Awaitable, type Identify, type PickPartial } from '../../common';
6
6
  import type { GatewayPresenceUpdateData, GatewaySendPayload } from '../../types';
7
7
  import { ConnectQueue } from '../structures/timeout';
8
8
  import { Heartbeater, type WorkerHeartbeaterMessages } from './heartbeater';
@@ -23,13 +23,18 @@ type WorkerManagerCustomRuntimeOptionalKeys = 'handleWorkerMessage' | 'handlePay
23
23
  type WorkerManagerRuntimeOptions = PickPartial<Required<WorkerManagerNativeOptions>, WorkerManagerRuntimeOptionalKeys> | (PickPartial<Required<Omit<WorkerManagerCustomOptions, 'path'>>, WorkerManagerCustomRuntimeOptionalKeys> & {
24
24
  path?: string;
25
25
  });
26
- export declare class WorkerManager extends Map<number, (ClusterWorker | WorkerThreadsWorker | {
27
- ready?: boolean;
28
- }) & {
26
+ type WorkerState = {
29
27
  ready?: boolean;
30
28
  disconnected?: boolean;
31
29
  resharded?: boolean;
32
- }> {
30
+ };
31
+ type CustomWorker = {
32
+ ready?: boolean;
33
+ } & WorkerState;
34
+ type ManagedWorker = (ClusterWorker | WorkerThreadsWorker | {
35
+ ready?: boolean;
36
+ }) & WorkerState;
37
+ export declare class WorkerManager extends Map<number, ManagedWorker> {
33
38
  static prepareSpaces(options: {
34
39
  shardStart: number;
35
40
  shardEnd: number;
@@ -38,17 +43,19 @@ export declare class WorkerManager extends Map<number, (ClusterWorker | WorkerTh
38
43
  options: WorkerManagerRuntimeOptions;
39
44
  debugger?: Logger;
40
45
  connectQueue: ConnectQueue;
41
- workerQueue: (() => void)[];
46
+ workerQueue: (() => Awaitable<void>)[];
42
47
  cacheAdapter: Adapter;
43
48
  promises: Map<string, {
44
49
  resolve: (value: any) => void;
45
50
  timeout: NodeJS.Timeout;
46
51
  }>;
47
52
  rest: ApiHandler;
48
- reshardingWorkerQueue: (() => void)[];
53
+ reshardingWorkerQueue: (() => Awaitable<void>)[];
49
54
  private _info?;
50
55
  heartbeater: Heartbeater;
51
56
  constructor(options: WorkerManagerConstructorOptions);
57
+ private reportError;
58
+ private observe;
52
59
  setCache(adapter: Adapter): void;
53
60
  setRest(rest: ApiHandler): void;
54
61
  get remaining(): number;
@@ -68,19 +75,25 @@ export declare class WorkerManager extends Map<number, (ClusterWorker | WorkerTh
68
75
  calculateShardId(guildId: string): number;
69
76
  private createWorkerNotFoundError;
70
77
  calculateWorkerId(shardId: number): number;
71
- postMessage(id: number, body: ManagerMessages | WorkerHeartbeaterMessages): void;
78
+ postMessage(id: number, body: ManagerMessages | WorkerHeartbeaterMessages): Promise<void>;
72
79
  prepareWorkers(shards: number[][], rawResharding?: boolean): void;
73
- createWorker(workerData: WorkerData): WorkerThreadsWorker | ({
74
- ready?: boolean;
75
- } & {
76
- ready?: boolean;
77
- disconnected?: boolean;
78
- resharded?: boolean;
79
- }) | cluster.Worker;
80
- spawn(workerId: number, shardId: number, resharding?: boolean): void;
80
+ createWorker(workerData: WorkerData & {
81
+ mode: 'threads';
82
+ }): WorkerThreadsWorker;
83
+ createWorker(workerData: WorkerData & {
84
+ mode: 'clusters';
85
+ }): ClusterWorker;
86
+ createWorker(workerData: WorkerData & {
87
+ mode: 'custom';
88
+ }): Awaitable<CustomWorker>;
89
+ createWorker(workerData: WorkerData): Awaitable<ManagedWorker>;
90
+ private createWorkerAsync;
91
+ private createWorkerInternal;
92
+ spawn(workerId: number, shardId: number, resharding?: boolean): Promise<void>;
81
93
  handleWorkerMessage(message: WorkerMessages): Promise<void>;
82
94
  private generateNonce;
83
95
  private generateSendPromise;
96
+ private sendRequest;
84
97
  send(data: GatewaySendPayload, shardId: number): Promise<boolean>;
85
98
  private resolveSendPayload;
86
99
  getShardInfo(shardId: number): Promise<WorkerShardInfo>;
@@ -51,7 +51,23 @@ class WorkerManager extends Map {
51
51
  return oldFn(message);
52
52
  };
53
53
  }
54
- this.heartbeater = new heartbeater_1.Heartbeater(this.postMessage.bind(this), options.heartbeaterInterval ?? 15e3);
54
+ this.heartbeater = new heartbeater_1.Heartbeater((workerId, message) => {
55
+ this.observe(() => this.postMessage(workerId, message), `[Worker #${workerId}] heartbeat sending failed`);
56
+ }, options.heartbeaterInterval ?? 15e3);
57
+ }
58
+ reportError(message, error) {
59
+ this.debugger ??= new __1.Logger({ name: '[WorkerManager]' });
60
+ this.debugger.error(message, error);
61
+ }
62
+ observe(operation, message) {
63
+ try {
64
+ void Promise.resolve(operation()).catch(error => {
65
+ this.reportError(message, error);
66
+ });
67
+ }
68
+ catch (error) {
69
+ this.reportError(message, error);
70
+ }
55
71
  }
56
72
  setCache(adapter) {
57
73
  this.cacheAdapter = adapter;
@@ -118,21 +134,37 @@ class WorkerManager extends Map {
118
134
  }
119
135
  return Math.floor((shardId - minimumShardId) / this.shardsPerWorker);
120
136
  }
121
- postMessage(id, body) {
137
+ async postMessage(id, body) {
122
138
  const worker = this.get(id);
123
139
  if (!worker)
124
- return this.debugger?.error(`Worker ${id} does not exists.`);
140
+ throw this.createWorkerNotFoundError(id);
125
141
  switch (this.options.mode) {
126
- case 'clusters':
127
- if (worker.isConnected())
128
- worker.send(body);
129
- break;
142
+ case 'clusters': {
143
+ const clusterWorker = worker;
144
+ if (!clusterWorker.isConnected()) {
145
+ throw new common_1.SeyfertError('INTERNAL_ERROR', {
146
+ metadata: {
147
+ workerId: id,
148
+ mode: this.options.mode,
149
+ detail: `Cannot send to disconnected worker #${id}.`,
150
+ },
151
+ });
152
+ }
153
+ await new Promise((resolve, reject) => {
154
+ clusterWorker.send(body, error => {
155
+ if (error)
156
+ reject(error);
157
+ else
158
+ resolve();
159
+ });
160
+ });
161
+ return;
162
+ }
130
163
  case 'threads':
131
164
  worker.postMessage(body);
132
- break;
165
+ return;
133
166
  case 'custom':
134
- this.options.adapter.postMessage(id, body);
135
- break;
167
+ await this.options.adapter.postMessage(id, body);
136
168
  }
137
169
  }
138
170
  prepareWorkers(shards, rawResharding = false) {
@@ -142,8 +174,8 @@ class WorkerManager extends Map {
142
174
  metadata: { detail: 'Cannot prepare workers without worker_threads.' },
143
175
  });
144
176
  for (let i = 0; i < shards.length; i++) {
145
- const registerWorker = (resharding) => {
146
- const worker = this.createWorker({
177
+ const registerWorker = async (resharding) => {
178
+ const worker = await this.createWorkerAsync({
147
179
  path: this.options.path ?? '',
148
180
  debug: this.options.debug,
149
181
  token: this.options.token,
@@ -168,25 +200,39 @@ class WorkerManager extends Map {
168
200
  this.heartbeater.register(workerId, deadWorkerId => {
169
201
  this.heartbeater.unregister(deadWorkerId);
170
202
  this.delete(deadWorkerId);
171
- registerWorkerHeartbeat(registerWorker(resharding), resharding);
203
+ this.observe(() => registerWorker(resharding).then(newWorkerId => registerWorkerHeartbeat(newWorkerId, resharding)), `[Worker #${deadWorkerId}] replacement failed`);
172
204
  });
173
205
  };
174
206
  const workerExists = this.has(i);
175
207
  if (rawResharding || !workerExists) {
176
- this[rawResharding ? 'reshardingWorkerQueue' : 'workerQueue'].push(() => {
177
- registerWorkerHeartbeat(registerWorker(rawResharding), rawResharding);
208
+ this[rawResharding ? 'reshardingWorkerQueue' : 'workerQueue'].push(async () => {
209
+ registerWorkerHeartbeat(await registerWorker(rawResharding), rawResharding);
178
210
  });
179
211
  }
180
212
  }
181
213
  }
182
214
  createWorker(workerData) {
215
+ return this.createWorkerInternal(workerData);
216
+ }
217
+ createWorkerAsync(workerData) {
218
+ return Promise.resolve(this.createWorkerInternal(workerData));
219
+ }
220
+ createWorkerInternal(workerData) {
221
+ if (workerData.mode !== this.options.mode) {
222
+ throw new common_1.SeyfertError('INTERNAL_ERROR', {
223
+ metadata: {
224
+ workerId: workerData.workerId,
225
+ detail: `Worker mode ${workerData.mode} does not match manager mode ${this.options.mode}.`,
226
+ },
227
+ });
228
+ }
183
229
  if (this.has(workerData.workerId)) {
230
+ const worker = this.get(workerData.workerId);
184
231
  if (workerData.resharding) {
185
- this.postMessage(workerData.workerId, {
232
+ this.observe(() => this.postMessage(workerData.workerId, {
186
233
  type: 'WORKER_ALREADY_EXISTS_RESHARDING',
187
- });
234
+ }), `[Worker #${workerData.workerId}] resharding restart message sending failed`);
188
235
  }
189
- const worker = this.get(workerData.workerId);
190
236
  return worker;
191
237
  }
192
238
  const worker_threads = (0, common_1.lazyLoadPackage)('node:worker_threads');
@@ -209,7 +255,7 @@ class WorkerManager extends Map {
209
255
  const worker = new worker_threads.Worker(workerData.path, {
210
256
  env: { ...process.env, ...env },
211
257
  });
212
- worker.on('message', data => this.handleWorkerMessage(data));
258
+ worker.on('message', data => this.observe(() => this.handleWorkerMessage(data), `[Worker #${workerData.workerId}] message handling failed`));
213
259
  worker.on('error', err => {
214
260
  this.debugger?.error(`[Worker #${workerData.workerId}]`, err);
215
261
  });
@@ -220,28 +266,46 @@ class WorkerManager extends Map {
220
266
  exec: workerData.path,
221
267
  });
222
268
  const worker = node_cluster_1.default.fork(env);
223
- worker.on('message', data => this.handleWorkerMessage(data));
269
+ worker.on('message', data => this.observe(() => this.handleWorkerMessage(data), `[Worker #${workerData.workerId}] message handling failed`));
224
270
  return worker;
225
271
  }
226
272
  case 'custom': {
227
- this.options.adapter.spawn(workerData, env);
228
- return {
273
+ const worker = {
229
274
  ready: false,
230
275
  };
276
+ this.set(workerData.workerId, worker);
277
+ try {
278
+ const spawnResult = this.options.adapter.spawn(workerData, env);
279
+ if (!(0, common_1.isPromiseLike)(spawnResult))
280
+ return worker;
281
+ return Promise.resolve(spawnResult).then(() => worker, error => {
282
+ if (this.get(workerData.workerId) === worker)
283
+ this.delete(workerData.workerId);
284
+ throw error;
285
+ });
286
+ }
287
+ catch (error) {
288
+ if (this.get(workerData.workerId) === worker)
289
+ this.delete(workerData.workerId);
290
+ throw error;
291
+ }
231
292
  }
232
293
  }
233
294
  }
234
295
  spawn(workerId, shardId, resharding = false) {
235
- this.connectQueue.push(() => {
236
- const worker = this.has(workerId);
237
- if (!worker) {
238
- this.debugger?.fatal(`Trying ${resharding ? 'reshard' : 'spawn'} with worker that doesn't exist`);
239
- return;
240
- }
241
- this.postMessage(workerId, {
242
- type: resharding ? 'ALLOW_CONNECT_RESHARDING' : 'ALLOW_CONNECT',
243
- shardId,
244
- presence: this.options.presence?.(shardId, workerId),
296
+ return new Promise((resolve, reject) => {
297
+ this.connectQueue.push(() => {
298
+ if (!this.has(workerId)) {
299
+ const error = this.createWorkerNotFoundError(workerId);
300
+ this.debugger?.fatal(`Trying ${resharding ? 'reshard' : 'spawn'} with worker that doesn't exist`);
301
+ reject(error);
302
+ return;
303
+ }
304
+ void this.postMessage(workerId, {
305
+ type: resharding ? 'ALLOW_CONNECT_RESHARDING' : 'ALLOW_CONNECT',
306
+ shardId,
307
+ presence: this.options.presence?.(shardId, workerId),
308
+ }).then(resolve, reject);
245
309
  });
246
310
  });
247
311
  }
@@ -255,7 +319,7 @@ class WorkerManager extends Map {
255
319
  this.get(message.workerId).resharded = true;
256
320
  if (!this.reshardingWorkerQueue.length && [...this.values()].every(w => w.resharded)) {
257
321
  for (const [id] of this.entries()) {
258
- this.postMessage(id, {
322
+ await this.postMessage(id, {
259
323
  type: 'DISCONNECT_ALL_SHARDS_RESHARDING',
260
324
  });
261
325
  }
@@ -267,7 +331,7 @@ class WorkerManager extends Map {
267
331
  const nextWorker = this.reshardingWorkerQueue.shift();
268
332
  if (nextWorker) {
269
333
  this.debugger?.info('Spawning next worker to reshard');
270
- nextWorker();
334
+ this.observe(nextWorker, 'Resharding worker spawn failed');
271
335
  }
272
336
  else {
273
337
  this.debugger?.info('No more workers to reshard left');
@@ -284,7 +348,7 @@ class WorkerManager extends Map {
284
348
  this.options.workers = this.size;
285
349
  delete this._info;
286
350
  for (const [id] of this.entries()) {
287
- this.postMessage(id, {
351
+ await this.postMessage(id, {
288
352
  type: 'CONNECT_ALL_SHARDS_RESHARDING',
289
353
  totalShards: this.options.totalShards,
290
354
  });
@@ -297,7 +361,7 @@ class WorkerManager extends Map {
297
361
  break;
298
362
  case 'WORKER_START_RESHARDING':
299
363
  {
300
- this.postMessage(message.workerId, {
364
+ await this.postMessage(message.workerId, {
301
365
  type: 'SPAWN_SHARDS_RESHARDING',
302
366
  compress: this.options.compress ?? false,
303
367
  info: {
@@ -313,7 +377,7 @@ class WorkerManager extends Map {
313
377
  break;
314
378
  case 'WORKER_START':
315
379
  {
316
- this.postMessage(message.workerId, {
380
+ await this.postMessage(message.workerId, {
317
381
  type: 'SPAWN_SHARDS',
318
382
  compress: this.options.compress ?? false,
319
383
  info: {
@@ -328,10 +392,10 @@ class WorkerManager extends Map {
328
392
  }
329
393
  break;
330
394
  case 'CONNECT_QUEUE_RESHARDING':
331
- this.spawn(message.workerId, message.shardId, true);
395
+ this.observe(() => this.spawn(message.workerId, message.shardId, true), `[Worker #${message.workerId}] resharding shard connection failed`);
332
396
  break;
333
397
  case 'CONNECT_QUEUE':
334
- this.spawn(message.workerId, message.shardId);
398
+ await this.spawn(message.workerId, message.shardId);
335
399
  break;
336
400
  case 'CACHE_REQUEST':
337
401
  {
@@ -350,7 +414,7 @@ class WorkerManager extends Map {
350
414
  catch (error) {
351
415
  response = { type: 'CACHE_RESULT', nonce: message.nonce, error: (0, worker_errors_1.serializeWorkerError)(error) };
352
416
  }
353
- this.postMessage(message.workerId, response);
417
+ await this.postMessage(message.workerId, response);
354
418
  }
355
419
  break;
356
420
  case 'RECEIVE_PAYLOAD':
@@ -395,7 +459,7 @@ class WorkerManager extends Map {
395
459
  {
396
460
  this.get(message.workerId).ready = true;
397
461
  if (this.size === this.totalWorkers && [...this.values()].every(w => w.ready)) {
398
- this.postMessage(this.keys().next().value, {
462
+ await this.postMessage(this.keys().next().value, {
399
463
  type: 'BOT_READY',
400
464
  });
401
465
  this.forEach(w => {
@@ -409,7 +473,7 @@ class WorkerManager extends Map {
409
473
  const nextWorker = this.workerQueue.shift();
410
474
  if (nextWorker) {
411
475
  this.debugger?.info('Spawning next worker');
412
- nextWorker();
476
+ await nextWorker();
413
477
  }
414
478
  else {
415
479
  this.debugger?.info('No more workers to spawn left');
@@ -429,7 +493,7 @@ class WorkerManager extends Map {
429
493
  const response = await this.rest.request(message.method, message.url, message.requestOptions);
430
494
  const encodedResponse = response instanceof ArrayBuffer ? Array.from(new Uint8Array(response)) : response;
431
495
  const responseType = response instanceof ArrayBuffer ? 'arrayBuffer' : undefined;
432
- this.postMessage(message.workerId, {
496
+ await this.postMessage(message.workerId, {
433
497
  nonce: message.nonce,
434
498
  response: encodedResponse,
435
499
  responseType,
@@ -452,18 +516,18 @@ class WorkerManager extends Map {
452
516
  case 'EVAL_TO_WORKER':
453
517
  {
454
518
  const nonce = this.generateNonce();
455
- this.postMessage(message.toWorkerId, {
519
+ const response = await this.sendRequest(nonce, 'Worker evaluation', () => this.postMessage(message.toWorkerId, {
456
520
  nonce,
457
521
  func: message.func,
458
522
  type: 'EXECUTE_EVAL_TO_WORKER',
459
523
  toWorkerId: message.toWorkerId,
460
524
  vars: message.vars,
461
- });
462
- this.generateSendPromise(nonce, 'Worker evaluation').then(val => this.postMessage(message.workerId, {
525
+ }));
526
+ await this.postMessage(message.workerId, {
463
527
  nonce: message.nonce,
464
- response: val,
528
+ response,
465
529
  type: 'EVAL_RESPONSE',
466
- }));
530
+ });
467
531
  }
468
532
  break;
469
533
  }
@@ -485,6 +549,21 @@ class WorkerManager extends Map {
485
549
  this.promises.set(nonce, { resolve: res, timeout });
486
550
  });
487
551
  }
552
+ async sendRequest(nonce, operation, send) {
553
+ const response = this.generateSendPromise(nonce, operation);
554
+ try {
555
+ const [result] = await Promise.all([response, send()]);
556
+ return result;
557
+ }
558
+ catch (error) {
559
+ const pending = this.promises.get(nonce);
560
+ if (pending) {
561
+ this.promises.delete(nonce);
562
+ clearTimeout(pending.timeout);
563
+ }
564
+ throw error;
565
+ }
566
+ }
488
567
  async send(data, shardId) {
489
568
  const workerId = this.calculateWorkerId(shardId);
490
569
  const worker = this.has(workerId);
@@ -495,13 +574,12 @@ class WorkerManager extends Map {
495
574
  if (!payload)
496
575
  return false;
497
576
  const nonce = this.generateNonce();
498
- this.postMessage(workerId, {
577
+ return this.sendRequest(nonce, 'Shard payload send', () => this.postMessage(workerId, {
499
578
  type: 'SEND_PAYLOAD',
500
579
  shardId,
501
580
  nonce,
502
581
  ...payload,
503
- });
504
- return this.generateSendPromise(nonce, 'Shard payload send');
582
+ }));
505
583
  }
506
584
  async resolveSendPayload(shardId, payload) {
507
585
  const result = await this.options.handleSendPayload?.(shardId, payload);
@@ -516,8 +594,7 @@ class WorkerManager extends Map {
516
594
  throw this.createWorkerNotFoundError(workerId);
517
595
  }
518
596
  const nonce = this.generateNonce();
519
- this.postMessage(workerId, { shardId, nonce, type: 'SHARD_INFO' });
520
- return this.generateSendPromise(nonce, 'Shard info request');
597
+ return this.sendRequest(nonce, 'Shard info request', () => this.postMessage(workerId, { shardId, nonce, type: 'SHARD_INFO' }));
521
598
  }
522
599
  async getWorkerInfo(workerId) {
523
600
  const worker = this.has(workerId);
@@ -525,18 +602,16 @@ class WorkerManager extends Map {
525
602
  throw this.createWorkerNotFoundError(workerId);
526
603
  }
527
604
  const nonce = this.generateNonce();
528
- this.postMessage(workerId, { nonce, type: 'WORKER_INFO' });
529
- return this.generateSendPromise(nonce, 'Worker info request');
605
+ return this.sendRequest(nonce, 'Worker info request', () => this.postMessage(workerId, { nonce, type: 'WORKER_INFO' }));
530
606
  }
531
- tellWorker(workerId, func, vars) {
607
+ async tellWorker(workerId, func, vars) {
532
608
  const nonce = this.generateNonce();
533
- this.postMessage(workerId, {
609
+ return this.sendRequest(nonce, 'Worker request', () => this.postMessage(workerId, {
534
610
  type: 'EXECUTE_EVAL',
535
611
  func: func.toString(),
536
612
  nonce,
537
613
  vars: JSON.stringify(vars),
538
- });
539
- return this.generateSendPromise(nonce);
614
+ }));
540
615
  }
541
616
  tellWorkers(func, vars) {
542
617
  const promises = [];
@@ -583,7 +658,7 @@ class WorkerManager extends Map {
583
658
  }, this.debugger);
584
659
  this.prepareWorkers(spaces);
585
660
  // Start workers queue
586
- this.workerQueue.shift()();
661
+ await this.workerQueue.shift()();
587
662
  await this.startResharding();
588
663
  }
589
664
  async startResharding() {
@@ -610,7 +685,9 @@ class WorkerManager extends Map {
610
685
  shardStart: 0,
611
686
  }, this.debugger);
612
687
  this.prepareWorkers(spaces, true);
613
- return this.reshardingWorkerQueue.shift()();
688
+ const firstWorker = this.reshardingWorkerQueue.shift();
689
+ if (firstWorker)
690
+ this.observe(firstWorker, 'Resharding worker spawn failed');
614
691
  }, this.options.resharding.interval);
615
692
  }
616
693
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seyfert",
3
- "version": "5.1.0",
3
+ "version": "5.1.1-dev-34481657717.0",
4
4
  "description": "The most advanced framework for discord bots",
5
5
  "packageManager": "pnpm@11.11.0",
6
6
  "workspaces": [