diff --git a/packages/discord.js/src/client/BaseClient.js b/packages/discord.js/src/client/BaseClient.js index d9473c4a9..3f26270da 100644 --- a/packages/discord.js/src/client/BaseClient.js +++ b/packages/discord.js/src/client/BaseClient.js @@ -106,6 +106,10 @@ class BaseClient extends EventEmitter { toJSON(...props) { return flatten(this, ...props); } + + async [Symbol.asyncDispose]() { + await this.destroy(); + } } module.exports = BaseClient; diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 67212096e..8e927d162 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -1,7 +1,10 @@ 'use strict'; +const { polyfillDispose } = require('@discordjs/util'); const { __exportStar } = require('tslib'); +polyfillDispose(); + // "Root" classes (starting points) exports.BaseClient = require('./client/BaseClient'); exports.Client = require('./client/Client'); diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 2ce5f5965..92beb12bd 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -517,7 +517,7 @@ export abstract class Base { public valueOf(): string; } -export class BaseClient extends EventEmitter { +export class BaseClient extends EventEmitter implements AsyncDisposable { public constructor(options?: ClientOptions | WebhookClientOptions); private decrementMaxListeners(): void; private incrementMaxListeners(): void; @@ -526,6 +526,7 @@ export class BaseClient extends EventEmitter { public rest: REST; public destroy(): void; public toJSON(...props: Record[]): unknown; + public [Symbol.asyncDispose](): Promise; } export type GuildCacheMessage = CacheTypeReducer< diff --git a/packages/util/src/functions/index.ts b/packages/util/src/functions/index.ts index 6010ae554..4c4c67ae5 100644 --- a/packages/util/src/functions/index.ts +++ b/packages/util/src/functions/index.ts @@ -3,3 +3,4 @@ export * from './range.js'; export * from './calculateShardId.js'; export * from './runtime.js'; export * from './userAgentAppendix.js'; +export * from './polyfillDispose.js'; diff --git a/packages/util/src/functions/polyfillDispose.ts b/packages/util/src/functions/polyfillDispose.ts new file mode 100644 index 000000000..8f3977ceb --- /dev/null +++ b/packages/util/src/functions/polyfillDispose.ts @@ -0,0 +1,14 @@ +/** + * Polyfill for `Symbol.dispose` and `Symbol.asyncDispose` which is used as a part of + * {@link https://github.com/tc39/proposal-explicit-resource-management}. Node versions below 18.x + * don't have these symbols by default, so we need to polyfill them. + */ +export function polyfillDispose() { + // Polyfill for `Symbol.dispose` and `Symbol.asyncDispose` if not available. + // Taken from https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management + + // @ts-expect-error This is a polyfill, so it's fine to write + Symbol.dispose ??= Symbol('Symbol.dispose'); + // @ts-expect-error Same as above + Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose'); +} diff --git a/packages/ws/src/ws/WebSocketManager.ts b/packages/ws/src/ws/WebSocketManager.ts index eb8976738..f4a80bec7 100644 --- a/packages/ws/src/ws/WebSocketManager.ts +++ b/packages/ws/src/ws/WebSocketManager.ts @@ -1,5 +1,6 @@ import type { REST } from '@discordjs/rest'; import { range, type Awaitable } from '@discordjs/util'; +import { polyfillDispose } from '@discordjs/util'; import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter'; import { Routes, @@ -17,6 +18,9 @@ import type { IIdentifyThrottler } from '../throttling/IIdentifyThrottler.js'; import { DefaultWebSocketManagerOptions, type CompressionMethod, type Encoding } from '../utils/constants.js'; import type { WebSocketShardDestroyOptions, WebSocketShardEvents } from './WebSocketShard.js'; +// We put this here because in index.ts WebSocketManager seems to be outputted before polyfillDispose() is called from tsup. +polyfillDispose(); + /** * Represents a range of shard ids */ @@ -199,7 +203,7 @@ export interface ManagerShardEventsMap { ]; } -export class WebSocketManager extends AsyncEventEmitter { +export class WebSocketManager extends AsyncEventEmitter implements AsyncDisposable { /** * The options being used by this manager */ @@ -334,4 +338,8 @@ export class WebSocketManager extends AsyncEventEmitter { public fetchStatus() { return this.strategy.fetchStatus(); } + + public async [Symbol.asyncDispose]() { + await this.destroy(); + } } diff --git a/tsconfig.json b/tsconfig.json index 4b64aaf28..50e09b90c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -42,7 +42,7 @@ // Language and Environment "experimentalDecorators": true, - "lib": ["ESNext"], + "lib": ["ESNext", "esnext.disposable"], "target": "ESNext", "useDefineForClassFields": true },