From 7349a6ee3e52cd349ccd3ef3c7aab6e37902c603 Mon Sep 17 00:00:00 2001 From: Danial Raza Date: Fri, 14 Nov 2025 23:03:39 +0100 Subject: [PATCH] refactor(client)!: remove `BaseClient` (#11274) BREAKING CHANGE: `BaseClient` has been removed and its functionality has been merged into `Client`. --- packages/discord.js/src/client/BaseClient.js | 131 ------------------- packages/discord.js/src/client/Client.js | 121 +++++++++++++++-- packages/discord.js/src/index.js | 1 - packages/discord.js/typings/index.d.ts | 21 ++- 4 files changed, 115 insertions(+), 159 deletions(-) delete mode 100644 packages/discord.js/src/client/BaseClient.js diff --git a/packages/discord.js/src/client/BaseClient.js b/packages/discord.js/src/client/BaseClient.js deleted file mode 100644 index ba8fb214e..000000000 --- a/packages/discord.js/src/client/BaseClient.js +++ /dev/null @@ -1,131 +0,0 @@ -'use strict'; - -const { REST, RESTEvents } = require('@discordjs/rest'); -const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter'); -const { Routes } = require('discord-api-types/v10'); -const { DiscordjsTypeError, ErrorCodes } = require('../errors/index.js'); -const { Events } = require('../util/Events.js'); -const { Options } = require('../util/Options.js'); -const { flatten } = require('../util/Util.js'); - -/** - * The base class for all clients. - * - * @extends {AsyncEventEmitter} - */ -class BaseClient extends AsyncEventEmitter { - constructor(options = {}) { - super(); - - if (typeof options !== 'object' || options === null) { - throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); - } - - const defaultOptions = Options.createDefault(); - /** - * The options the client was instantiated with - * - * @type {ClientOptions} - */ - this.options = { - ...defaultOptions, - ...options, - presence: { - ...defaultOptions.presence, - ...options.presence, - }, - sweepers: { - ...defaultOptions.sweepers, - ...options.sweepers, - }, - ws: { - ...defaultOptions.ws, - ...options.ws, - }, - rest: { - ...defaultOptions.rest, - ...options.rest, - userAgentAppendix: options.rest?.userAgentAppendix - ? `${Options.userAgentAppendix} ${options.rest.userAgentAppendix}` - : Options.userAgentAppendix, - }, - }; - - /** - * The REST manager of the client - * - * @type {REST} - */ - this.rest = new REST(this.options.rest); - - this.rest.on(RESTEvents.Debug, message => this.emit(Events.Debug, message)); - } - - /** - * Destroys all assets used by the base client. - * - * @returns {void} - */ - destroy() { - this.rest.clearHashSweeper(); - this.rest.clearHandlerSweeper(); - } - - /** - * Options used for deleting a webhook. - * - * @typedef {Object} WebhookDeleteOptions - * @property {string} [token] Token of the webhook - * @property {string} [reason] The reason for deleting the webhook - */ - - /** - * Deletes a webhook. - * - * @param {Snowflake} id The webhook's id - * @param {WebhookDeleteOptions} [options] Options for deleting the webhook - * @returns {Promise} - */ - async deleteWebhook(id, { token, reason } = {}) { - await this.rest.delete(Routes.webhook(id, token), { auth: !token, reason }); - } - - /** - * Increments max listeners by one, if they are not zero. - * - * @private - */ - incrementMaxListeners() { - const maxListeners = this.getMaxListeners(); - if (maxListeners !== 0) { - this.setMaxListeners(maxListeners + 1); - } - } - - /** - * Decrements max listeners by one, if they are not zero. - * - * @private - */ - decrementMaxListeners() { - const maxListeners = this.getMaxListeners(); - if (maxListeners !== 0) { - this.setMaxListeners(maxListeners - 1); - } - } - - toJSON(...props) { - return flatten(this, ...props); - } - - async [Symbol.asyncDispose]() { - await this.destroy(); - } -} - -exports.BaseClient = BaseClient; - -/** - * @external REST - * @see {@link https://discord.js.org/docs/packages/rest/stable/REST:Class} - */ diff --git a/packages/discord.js/src/client/Client.js b/packages/discord.js/src/client/Client.js index bd53a2b91..ad0a03c63 100644 --- a/packages/discord.js/src/client/Client.js +++ b/packages/discord.js/src/client/Client.js @@ -3,8 +3,9 @@ const process = require('node:process'); const { clearTimeout, setImmediate, setTimeout } = require('node:timers'); const { Collection } = require('@discordjs/collection'); -const { makeURLSearchParams } = require('@discordjs/rest'); +const { REST, RESTEvents, makeURLSearchParams } = require('@discordjs/rest'); const { WebSocketManager, WebSocketShardEvents, WebSocketShardStatus } = require('@discordjs/ws'); +const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter'); const { GatewayDispatchEvents, GatewayIntentBits, OAuth2Scopes, Routes } = require('discord-api-types/v10'); const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors/index.js'); const { ChannelManager } = require('../managers/ChannelManager.js'); @@ -28,7 +29,7 @@ const { Options } = require('../util/Options.js'); const { PermissionsBitField } = require('../util/PermissionsBitField.js'); const { Status } = require('../util/Status.js'); const { Sweepers } = require('../util/Sweepers.js'); -const { BaseClient } = require('./BaseClient.js'); +const { flatten } = require('../util/Util.js'); const { ActionsManager } = require('./actions/ActionsManager.js'); const { ClientVoiceManager } = require('./voice/ClientVoiceManager.js'); const { PacketHandlers } = require('./websocket/handlers/index.js'); @@ -47,24 +48,66 @@ const BeforeReadyWhitelist = [ /** * The main hub for interacting with the Discord API, and the starting point for any bot. * - * @extends {BaseClient} + * @extends {AsyncEventEmitter} */ -class Client extends BaseClient { +class Client extends AsyncEventEmitter { /** * @param {ClientOptions} options Options for the client */ constructor(options) { - super(options); + super(); + + if (typeof options !== 'object' || options === null) { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); + } + + const defaultOptions = Options.createDefault(); + /** + * The options the client was instantiated with + * + * @type {ClientOptions} + */ + this.options = { + ...defaultOptions, + ...options, + presence: { + ...defaultOptions.presence, + ...options.presence, + }, + sweepers: { + ...defaultOptions.sweepers, + ...options.sweepers, + }, + ws: { + ...defaultOptions.ws, + ...options.ws, + }, + rest: { + ...defaultOptions.rest, + ...options.rest, + userAgentAppendix: options.rest?.userAgentAppendix + ? `${Options.userAgentAppendix} ${options.rest.userAgentAppendix}` + : Options.userAgentAppendix, + }, + }; + + /** + * The REST manager of the client + * + * @type {REST} + */ + this.rest = new REST(this.options.rest); + + this.rest.on(RESTEvents.Debug, message => this.emit(Events.Debug, message)); const data = require('node:worker_threads').workerData ?? process.env; - const defaults = Options.createDefault(); - if (this.options.ws.shardIds === defaults.ws.shardIds && 'SHARDS' in data) { + if (this.options.ws.shardIds === defaultOptions.ws.shardIds && 'SHARDS' in data) { const shards = JSON.parse(data.SHARDS); this.options.ws.shardIds = Array.isArray(shards) ? shards : [shards]; } - if (this.options.ws.shardCount === defaults.ws.shardCount && 'SHARD_COUNT' in data) { + if (this.options.ws.shardCount === defaultOptions.ws.shardCount && 'SHARD_COUNT' in data) { this.options.ws.shardCount = Number(data.SHARD_COUNT); } @@ -442,12 +485,56 @@ class Client extends BaseClient { } /** - * Logs out, terminates the connection to Discord, and destroys the client. + * Options used for deleting a webhook. + * + * @typedef {Object} WebhookDeleteOptions + * @property {string} [token] Token of the webhook + * @property {string} [reason] The reason for deleting the webhook + */ + + /** + * Deletes a webhook. + * + * @param {Snowflake} id The webhook's id + * @param {WebhookDeleteOptions} [options] Options for deleting the webhook + * @returns {Promise} + */ + async deleteWebhook(id, { token, reason } = {}) { + await this.rest.delete(Routes.webhook(id, token), { auth: !token, reason }); + } + + /** + * Increments max listeners by one, if they are not zero. + * + * @private + */ + incrementMaxListeners() { + const maxListeners = this.getMaxListeners(); + if (maxListeners !== 0) { + this.setMaxListeners(maxListeners + 1); + } + } + + /** + * Decrements max listeners by one, if they are not zero. + * + * @private + */ + decrementMaxListeners() { + const maxListeners = this.getMaxListeners(); + if (maxListeners !== 0) { + this.setMaxListeners(maxListeners - 1); + } + } + + /** + * Destroys all assets used by the client. * * @returns {Promise} */ async destroy() { - super.destroy(); + this.rest.clearHashSweeper(); + this.rest.clearHandlerSweeper(); this.sweepers.destroy(); await this.ws.destroy(); @@ -701,10 +788,7 @@ class Client extends BaseClient { } toJSON() { - return super.toJSON({ - actions: false, - presence: false, - }); + return flatten(this, { actions: false, presence: false }); } /** @@ -797,6 +881,10 @@ class Client extends BaseClient { throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'jsonTransformer', 'a function'); } } + + async [Symbol.asyncDispose]() { + await this.destroy(); + } } exports.Client = Client; @@ -846,6 +934,11 @@ exports.Client = Client; * @see {@link https://discord.js.org/docs/packages/collection/stable/Collection:Class} */ +/** + * @external REST + * @see {@link https://discord.js.org/docs/packages/rest/stable/REST:Class} + */ + /** * @external ImageURLOptions * @see {@link https://discord.js.org/docs/packages/rest/stable/ImageURLOptions:Interface} diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 71bb83f79..edb1ad4c6 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -3,7 +3,6 @@ const { __exportStar } = require('tslib'); // "Root" classes (starting points) -exports.BaseClient = require('./client/BaseClient.js').BaseClient; exports.Client = require('./client/Client.js').Client; exports.Shard = require('./sharding/Shard.js').Shard; exports.ShardClientUtil = require('./sharding/ShardClientUtil.js').ShardClientUtil; diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index e3d1c1393..0cba25f74 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -492,18 +492,6 @@ export abstract class Base { public valueOf(): string; } -export class BaseClient extends AsyncEventEmitter implements AsyncDisposable { - public constructor(options?: ClientOptions); - private decrementMaxListeners(): void; - private incrementMaxListeners(): void; - - public options: ClientOptions; - public rest: REST; - public destroy(): void; - public toJSON(...props: Record[]): unknown; - public [Symbol.asyncDispose](): Promise; -} - export type GuildCacheMessage = CacheTypeReducer< Cached, Message, @@ -913,7 +901,10 @@ export type If = Value ex ? FalseResult : FalseResult | TrueResult; -export class Client extends BaseClient { +export class Client + extends AsyncEventEmitter + implements AsyncDisposable +{ public constructor(options: ClientOptions); private readonly actions: unknown; private readonly expectedGuilds: Set; @@ -928,6 +919,8 @@ export class Client extends BaseClient extends BaseClient; public readyTimestamp: If; + public rest: REST; public sweepers: Sweepers; public shard: ShardClientUtil | null; public status: Status; @@ -969,6 +963,7 @@ export class Client extends BaseClient; public isReady(): this is Client; public toJSON(): unknown; + public [Symbol.asyncDispose](): Promise; } export interface StickerPackFetchOptions {