refactor(client)!: remove BaseClient (#11274)

BREAKING CHANGE: `BaseClient` has been removed and its functionality has been merged into `Client`.
This commit is contained in:
Danial Raza
2025-11-14 23:03:39 +01:00
committed by GitHub
parent 87364c7ed9
commit 7349a6ee3e
4 changed files with 115 additions and 159 deletions

View File

@@ -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<void>}
*/
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}
*/

View File

@@ -3,8 +3,9 @@
const process = require('node:process'); const process = require('node:process');
const { clearTimeout, setImmediate, setTimeout } = require('node:timers'); const { clearTimeout, setImmediate, setTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection'); 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 { WebSocketManager, WebSocketShardEvents, WebSocketShardStatus } = require('@discordjs/ws');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { GatewayDispatchEvents, GatewayIntentBits, OAuth2Scopes, Routes } = require('discord-api-types/v10'); const { GatewayDispatchEvents, GatewayIntentBits, OAuth2Scopes, Routes } = require('discord-api-types/v10');
const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors/index.js'); const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors/index.js');
const { ChannelManager } = require('../managers/ChannelManager.js'); const { ChannelManager } = require('../managers/ChannelManager.js');
@@ -28,7 +29,7 @@ const { Options } = require('../util/Options.js');
const { PermissionsBitField } = require('../util/PermissionsBitField.js'); const { PermissionsBitField } = require('../util/PermissionsBitField.js');
const { Status } = require('../util/Status.js'); const { Status } = require('../util/Status.js');
const { Sweepers } = require('../util/Sweepers.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 { ActionsManager } = require('./actions/ActionsManager.js');
const { ClientVoiceManager } = require('./voice/ClientVoiceManager.js'); const { ClientVoiceManager } = require('./voice/ClientVoiceManager.js');
const { PacketHandlers } = require('./websocket/handlers/index.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. * 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 * @param {ClientOptions} options Options for the client
*/ */
constructor(options) { 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 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); const shards = JSON.parse(data.SHARDS);
this.options.ws.shardIds = Array.isArray(shards) ? shards : [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); 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<void>}
*/
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<void>} * @returns {Promise<void>}
*/ */
async destroy() { async destroy() {
super.destroy(); this.rest.clearHashSweeper();
this.rest.clearHandlerSweeper();
this.sweepers.destroy(); this.sweepers.destroy();
await this.ws.destroy(); await this.ws.destroy();
@@ -701,10 +788,7 @@ class Client extends BaseClient {
} }
toJSON() { toJSON() {
return super.toJSON({ return flatten(this, { actions: false, presence: false });
actions: false,
presence: false,
});
} }
/** /**
@@ -797,6 +881,10 @@ class Client extends BaseClient {
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'jsonTransformer', 'a function'); throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'jsonTransformer', 'a function');
} }
} }
async [Symbol.asyncDispose]() {
await this.destroy();
}
} }
exports.Client = Client; exports.Client = Client;
@@ -846,6 +934,11 @@ exports.Client = Client;
* @see {@link https://discord.js.org/docs/packages/collection/stable/Collection:Class} * @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 * @external ImageURLOptions
* @see {@link https://discord.js.org/docs/packages/rest/stable/ImageURLOptions:Interface} * @see {@link https://discord.js.org/docs/packages/rest/stable/ImageURLOptions:Interface}

View File

@@ -3,7 +3,6 @@
const { __exportStar } = require('tslib'); const { __exportStar } = require('tslib');
// "Root" classes (starting points) // "Root" classes (starting points)
exports.BaseClient = require('./client/BaseClient.js').BaseClient;
exports.Client = require('./client/Client.js').Client; exports.Client = require('./client/Client.js').Client;
exports.Shard = require('./sharding/Shard.js').Shard; exports.Shard = require('./sharding/Shard.js').Shard;
exports.ShardClientUtil = require('./sharding/ShardClientUtil.js').ShardClientUtil; exports.ShardClientUtil = require('./sharding/ShardClientUtil.js').ShardClientUtil;

View File

@@ -492,18 +492,6 @@ export abstract class Base {
public valueOf(): string; public valueOf(): string;
} }
export class BaseClient<Events extends {}> extends AsyncEventEmitter<Events> 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<string, boolean | string>[]): unknown;
public [Symbol.asyncDispose](): Promise<void>;
}
export type GuildCacheMessage<Cached extends CacheType> = CacheTypeReducer< export type GuildCacheMessage<Cached extends CacheType> = CacheTypeReducer<
Cached, Cached,
Message<true>, Message<true>,
@@ -913,7 +901,10 @@ export type If<Value extends boolean, TrueResult, FalseResult = null> = Value ex
? FalseResult ? FalseResult
: FalseResult | TrueResult; : FalseResult | TrueResult;
export class Client<Ready extends boolean = boolean> extends BaseClient<ClientEventTypes> { export class Client<Ready extends boolean = boolean>
extends AsyncEventEmitter<ClientEventTypes>
implements AsyncDisposable
{
public constructor(options: ClientOptions); public constructor(options: ClientOptions);
private readonly actions: unknown; private readonly actions: unknown;
private readonly expectedGuilds: Set<Snowflake>; private readonly expectedGuilds: Set<Snowflake>;
@@ -928,6 +919,8 @@ export class Client<Ready extends boolean = boolean> extends BaseClient<ClientEv
private _triggerClientReady(): void; private _triggerClientReady(): void;
private _validateOptions(options: ClientOptions): void; private _validateOptions(options: ClientOptions): void;
private get _censoredToken(): string | null; private get _censoredToken(): string | null;
private decrementMaxListeners(): void;
private incrementMaxListeners(): void;
// This a technique used to brand the ready state. Or else we'll get `never` errors on typeguard checks. // This a technique used to brand the ready state. Or else we'll get `never` errors on typeguard checks.
private readonly _ready: Ready; private readonly _ready: Ready;
@@ -939,6 +932,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient<ClientEv
public get ping(): number | null; public get ping(): number | null;
public get readyAt(): If<Ready, Date>; public get readyAt(): If<Ready, Date>;
public readyTimestamp: If<Ready, number>; public readyTimestamp: If<Ready, number>;
public rest: REST;
public sweepers: Sweepers; public sweepers: Sweepers;
public shard: ShardClientUtil | null; public shard: ShardClientUtil | null;
public status: Status; public status: Status;
@@ -969,6 +963,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient<ClientEv
public login(token?: string): Promise<string>; public login(token?: string): Promise<string>;
public isReady(): this is Client<true>; public isReady(): this is Client<true>;
public toJSON(): unknown; public toJSON(): unknown;
public [Symbol.asyncDispose](): Promise<void>;
} }
export interface StickerPackFetchOptions { export interface StickerPackFetchOptions {