Skip to content

Commit

Permalink
refactor!: use AsyncEventEmitter instead of EventEmitter
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The `BaseClient`, `Shard`, `ShardingManager`, and `Collector` classes now extend `AsyncEventEmitter` instead of `EventEmitter`.
  • Loading branch information
almeidx committed Jan 18, 2025
1 parent ad6b006 commit 9ad40b7
Show file tree
Hide file tree
Showing 9 changed files with 41 additions and 124 deletions.
1 change: 1 addition & 0 deletions packages/discord.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@discordjs/util": "workspace:^",
"@discordjs/ws": "workspace:^",
"@sapphire/snowflake": "3.5.5",
"@vladfrangu/async_event_emitter": "^2.4.6",
"discord-api-types": "^0.37.114",
"fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1",
Expand Down
8 changes: 4 additions & 4 deletions packages/discord.js/src/client/BaseClient.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
'use strict';

const EventEmitter = require('node:events');
const { REST } = require('@discordjs/rest');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { Routes } = require('discord-api-types/v10');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const { Options } = require('../util/Options');
const { flatten } = require('../util/Util');

/**
* The base class for all clients.
* @extends {EventEmitter}
* @extends {AsyncEventEmitter}
*/
class BaseClient extends EventEmitter {
class BaseClient extends AsyncEventEmitter {
constructor(options = {}) {
super({ captureRejections: true });
super();

if (typeof options !== 'object' || options === null) {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true);
Expand Down
10 changes: 5 additions & 5 deletions packages/discord.js/src/sharding/Shard.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use strict';

const EventEmitter = require('node:events');
const path = require('node:path');
const process = require('node:process');
const { setTimeout, clearTimeout } = require('node:timers');
const { setTimeout: sleep } = require('node:timers/promises');
const { SHARE_ENV } = require('node:worker_threads');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { DiscordjsError, ErrorCodes } = require('../errors');
const { ShardEvents } = require('../util/ShardEvents');
const { makeError, makePlainError } = require('../util/Util');
Expand All @@ -17,9 +17,9 @@ let Worker = null;
* A self-contained shard created by the {@link ShardingManager}. Each one has a {@link ChildProcess} that contains
* an instance of the bot and its {@link Client}. When its child process/worker exits for any reason, the shard will
* spawn a new one to replace it as necessary.
* @extends {EventEmitter}
* @extends {AsyncEventEmitter}
*/
class Shard extends EventEmitter {
class Shard extends AsyncEventEmitter {
constructor(manager, id) {
super();

Expand Down Expand Up @@ -445,7 +445,7 @@ class Shard extends EventEmitter {

/**
* Increments max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events.
* @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private
*/
incrementMaxListeners(emitter) {
Expand All @@ -457,7 +457,7 @@ class Shard extends EventEmitter {

/**
* Decrements max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events.
* @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private
*/
decrementMaxListeners(emitter) {
Expand Down
4 changes: 2 additions & 2 deletions packages/discord.js/src/sharding/ShardClientUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class ShardClientUtil {

/**
* Increments max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events.
* @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private
*/
incrementMaxListeners(emitter) {
Expand All @@ -254,7 +254,7 @@ class ShardClientUtil {

/**
* Decrements max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events.
* @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private
*/
decrementMaxListeners(emitter) {
Expand Down
6 changes: 3 additions & 3 deletions packages/discord.js/src/sharding/ShardingManager.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use strict';

const EventEmitter = require('node:events');
const fs = require('node:fs');
const path = require('node:path');
const process = require('node:process');
const { setTimeout: sleep } = require('node:timers/promises');
const { Collection } = require('@discordjs/collection');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { Shard } = require('./Shard');
const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors');
const { fetchRecommendedShardCount } = require('../util/Util');
Expand All @@ -17,9 +17,9 @@ const { fetchRecommendedShardCount } = require('../util/Util');
* process, and there are several useful methods that utilize it in order to simplify tasks that are normally difficult
* with sharding. It can spawn a specific number of shards or the amount that Discord suggests for the bot, and takes a
* path to your main bot script to launch for each one.
* @extends {EventEmitter}
* @extends {AsyncEventEmitter}
*/
class ShardingManager extends EventEmitter {
class ShardingManager extends AsyncEventEmitter {
/**
* The mode to spawn shards with for a {@link ShardingManager}. Can be either one of:
* * 'process' to use child processes
Expand Down
6 changes: 3 additions & 3 deletions packages/discord.js/src/structures/interfaces/Collector.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';

const EventEmitter = require('node:events');
const { setTimeout, clearTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { DiscordjsTypeError, ErrorCodes } = require('../../errors');
const { flatten } = require('../../util/Util');

Expand All @@ -25,10 +25,10 @@ const { flatten } = require('../../util/Util');

/**
* Abstract class for defining a new Collector.
* @extends {EventEmitter}
* @extends {AsyncEventEmitter}
* @abstract
*/
class Collector extends EventEmitter {
class Collector extends AsyncEventEmitter {
constructor(client, options = {}) {
super();

Expand Down
123 changes: 18 additions & 105 deletions packages/discord.js/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Awaitable, JSONEncodable } from '@discordjs/util';
import { Collection, ReadonlyCollection } from '@discordjs/collection';
import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest';
import { WebSocketManager, WebSocketManagerOptions } from '@discordjs/ws';
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
import {
APIActionRowComponent,
APIApplicationCommandInteractionData,
Expand Down Expand Up @@ -177,7 +178,6 @@ import {
GatewayVoiceChannelEffectSendDispatchData,
} from 'discord-api-types/v10';
import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events';
import { Stream } from 'node:stream';
import { MessagePort, Worker } from 'node:worker_threads';
import {
Expand Down Expand Up @@ -515,7 +515,7 @@ export abstract class Base {
public valueOf(): string;
}

export class BaseClient extends EventEmitter implements AsyncDisposable {
export class BaseClient<Events extends {}> extends AsyncEventEmitter<Events> implements AsyncDisposable {
public constructor(options?: ClientOptions | WebhookClientOptions);
private decrementMaxListeners(): void;
private incrementMaxListeners(): void;
Expand Down Expand Up @@ -959,7 +959,7 @@ export type If<Value extends boolean, TrueResult, FalseResult = null> = Value ex
? FalseResult
: TrueResult | FalseResult;

export class Client<Ready extends boolean = boolean> extends BaseClient {
export class Client<Ready extends boolean = boolean> extends BaseClient<ClientEvents> {
public constructor(options: ClientOptions);
private actions: unknown;
private expectedGuilds: Set<Snowflake>;
Expand All @@ -977,18 +977,6 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
// This a technique used to brand the ready state. Or else we'll get `never` errors on typeguard checks.
private readonly _ready: Ready;

// Override inherited static EventEmitter methods, with added type checks for Client events.
public static once<Emitter extends EventEmitter, Event extends keyof ClientEvents>(
eventEmitter: Emitter,
eventName: Emitter extends Client ? Event : string | symbol,
options?: { signal?: AbortSignal | undefined },
): Promise<Emitter extends Client ? ClientEvents[Event] : any[]>;
public static on<Emitter extends EventEmitter, Event extends keyof ClientEvents>(
eventEmitter: Emitter,
eventName: Emitter extends Client ? Event : string | symbol,
options?: { signal?: AbortSignal | undefined },
): AsyncIterableIterator<Emitter extends Client ? ClientEvents[Event] : any[]>;

public application: If<Ready, ClientApplication>;
public channels: ChannelManager;
public get emojis(): BaseGuildEmojiManager;
Expand Down Expand Up @@ -1023,30 +1011,6 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
public login(token?: string): Promise<string>;
public isReady(): this is Client<true>;
public toJSON(): unknown;

public on<Event extends keyof ClientEvents>(event: Event, listener: (...args: ClientEvents[Event]) => void): this;
public on<Event extends string | symbol>(
event: Exclude<Event, keyof ClientEvents>,
listener: (...args: any[]) => void,
): this;

public once<Event extends keyof ClientEvents>(event: Event, listener: (...args: ClientEvents[Event]) => void): this;
public once<Event extends string | symbol>(
event: Exclude<Event, keyof ClientEvents>,
listener: (...args: any[]) => void,
): this;

public emit<Event extends keyof ClientEvents>(event: Event, ...args: ClientEvents[Event]): boolean;
public emit<Event extends string | symbol>(event: Exclude<Event, keyof ClientEvents>, ...args: unknown[]): boolean;

public off<Event extends keyof ClientEvents>(event: Event, listener: (...args: ClientEvents[Event]) => void): this;
public off<Event extends string | symbol>(
event: Exclude<Event, keyof ClientEvents>,
listener: (...args: any[]) => void,
): this;

public removeAllListeners<Event extends keyof ClientEvents>(event?: Event): this;
public removeAllListeners<Event extends string | symbol>(event?: Exclude<Event, keyof ClientEvents>): this;
}

export interface StickerPackFetchOptions {
Expand Down Expand Up @@ -1134,7 +1098,9 @@ export interface CollectorEventTypes<Key, Value, Extras extends unknown[] = []>
end: [collected: ReadonlyCollection<Key, Value>, reason: string];
}

export abstract class Collector<Key, Value, Extras extends unknown[] = []> extends EventEmitter {
export abstract class Collector<Key, Value, Extras extends unknown[] = []> extends AsyncEventEmitter<
CollectorEventTypes<Key, Value, Extras>
> {
protected constructor(client: Client<true>, options?: CollectorOptions<[Value, ...Extras]>);
private _timeout: NodeJS.Timeout | null;
private _idletimeout: NodeJS.Timeout | null;
Expand All @@ -1160,16 +1126,6 @@ export abstract class Collector<Key, Value, Extras extends unknown[] = []> exten
protected listener: (...args: any[]) => void;
public abstract collect(...args: unknown[]): Awaitable<Key | null>;
public abstract dispose(...args: unknown[]): Key | null;

public on<EventKey extends keyof CollectorEventTypes<Key, Value, Extras>>(
event: EventKey,
listener: (...args: CollectorEventTypes<Key, Value, Extras>[EventKey]) => void,
): this;

public once<EventKey extends keyof CollectorEventTypes<Key, Value, Extras>>(
event: EventKey,
listener: (...args: CollectorEventTypes<Key, Value, Extras>[EventKey]) => void,
): this;
}

export class ChatInputCommandInteraction<Cached extends CacheType = CacheType> extends CommandInteraction<Cached> {
Expand Down Expand Up @@ -2031,19 +1987,6 @@ export class InteractionCollector<Interaction extends CollectedInteraction> exte
public collect(interaction: Interaction): Snowflake;
public empty(): void;
public dispose(interaction: Interaction): Snowflake;
public on(event: 'collect' | 'dispose' | 'ignore', listener: (interaction: Interaction) => void): this;
public on(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, Interaction>, reason: string) => void,
): this;
public on(event: string, listener: (...args: any[]) => void): this;

public once(event: 'collect' | 'dispose' | 'ignore', listener: (interaction: Interaction) => void): this;
public once(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, Interaction>, reason: string) => void,
): this;
public once(event: string, listener: (...args: any[]) => void): this;
}

// tslint:disable-next-line no-empty-interface
Expand Down Expand Up @@ -2783,26 +2726,6 @@ export class ReactionCollector extends Collector<Snowflake | string, MessageReac
public collect(reaction: MessageReaction, user: User): Snowflake | string | null;
public dispose(reaction: MessageReaction, user: User): Snowflake | string | null;
public empty(): void;

public on(
event: 'collect' | 'dispose' | 'remove' | 'ignore',
listener: (reaction: MessageReaction, user: User) => void,
): this;
public on(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, MessageReaction>, reason: string) => void,
): this;
public on(event: string, listener: (...args: any[]) => void): this;

public once(
event: 'collect' | 'dispose' | 'remove' | 'ignore',
listener: (reaction: MessageReaction, user: User) => void,
): this;
public once(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, MessageReaction>, reason: string) => void,
): this;
public once(event: string, listener: (...args: any[]) => void): this;
}

export class ReactionEmoji extends Emoji {
Expand Down Expand Up @@ -2997,15 +2920,15 @@ export interface ShardEventTypes {
spawn: [process: ChildProcess | Worker];
}

export class Shard extends EventEmitter {
export class Shard extends AsyncEventEmitter<ShardEventTypes> {
private constructor(manager: ShardingManager, id: number);
private _evals: Map<string, Promise<unknown>>;
private _exitListener: (...args: any[]) => void;
private _fetches: Map<string, Promise<unknown>>;
private _handleExit(respawn?: boolean, timeout?: number): void;
private _handleMessage(message: unknown): void;
private incrementMaxListeners(emitter: EventEmitter | ChildProcess): void;
private decrementMaxListeners(emitter: EventEmitter | ChildProcess): void;
private incrementMaxListeners(emitter: Worker | ChildProcess): void;
private decrementMaxListeners(emitter: Worker | ChildProcess): void;

public args: string[];
public execArgv: string[];
Expand All @@ -3027,24 +2950,14 @@ export class Shard extends EventEmitter {
public respawn(options?: { delay?: number; timeout?: number }): Promise<ChildProcess>;
public send(message: unknown): Promise<Shard>;
public spawn(timeout?: number): Promise<ChildProcess>;

public on<Event extends keyof ShardEventTypes>(
event: Event,
listener: (...args: ShardEventTypes[Event]) => void,
): this;

public once<Event extends keyof ShardEventTypes>(
event: Event,
listener: (...args: ShardEventTypes[Event]) => void,
): this;
}

export class ShardClientUtil {
private constructor(client: Client<true>, mode: ShardingManagerMode);
private _handleMessage(message: unknown): void;
private _respond(type: string, message: unknown): void;
private incrementMaxListeners(emitter: EventEmitter | ChildProcess): void;
private decrementMaxListeners(emitter: EventEmitter | ChildProcess): void;
private incrementMaxListeners(emitter: Worker | ChildProcess): void;
private decrementMaxListeners(emitter: Worker | ChildProcess): void;

public client: Client;
public get count(): number;
Expand Down Expand Up @@ -3073,7 +2986,11 @@ export class ShardClientUtil {
public static shardIdForGuildId(guildId: Snowflake, shardCount: number): number;
}

export class ShardingManager extends EventEmitter {
export interface ShardingManagerEvents {
shardCreate: [shard: Shard];
}

export class ShardingManager extends AsyncEventEmitter<ShardingManagerEvents> {
public constructor(file: string, options?: ShardingManagerOptions);
private _performOnShards(method: string, args: readonly unknown[]): Promise<unknown[]>;
private _performOnShards(method: string, args: readonly unknown[], shard: number): Promise<unknown>;
Expand Down Expand Up @@ -3105,10 +3022,6 @@ export class ShardingManager extends EventEmitter {
public fetchClientValues(prop: string, shard: number): Promise<unknown>;
public respawnAll(options?: MultipleShardRespawnOptions): Promise<Collection<number, Shard>>;
public spawn(options?: MultipleShardSpawnOptions): Promise<Collection<number, Shard>>;

public on(event: 'shardCreate', listener: (shard: Shard) => void): this;

public once(event: 'shardCreate', listener: (shard: Shard) => void): this;
}

export interface FetchRecommendedShardCountOptions {
Expand Down Expand Up @@ -3692,8 +3605,8 @@ export class Webhook<Type extends WebhookType = WebhookType> {
}

// tslint:disable-next-line no-empty-interface
export interface WebhookClient extends WebhookFields, BaseClient {}
export class WebhookClient extends BaseClient {
export interface WebhookClient extends WebhookFields, BaseClient<{}> {}
export class WebhookClient extends BaseClient<{}> {
public constructor(data: WebhookClientData, options?: WebhookClientOptions);
public readonly client: this;
public options: WebhookClientOptions;
Expand Down
4 changes: 2 additions & 2 deletions packages/discord.js/typings/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1306,9 +1306,9 @@ client.on('guildCreate', async g => {
);
});

// EventEmitter static method overrides
// Event emitter static method overrides
expectType<Promise<[Client<true>]>>(Client.once(client, 'clientReady'));
expectType<AsyncIterableIterator<[Client<true>]>>(Client.on(client, 'clientReady'));
expectAssignable<AsyncIterableIterator<[Client<true>]>>(Client.on(client, 'clientReady'));

client.login('absolutely-valid-token');

Expand Down
Loading

0 comments on commit 9ad40b7

Please sign in to comment.