Skip to content

Commit 4206e35

Browse files
authored
feat(Client): add conditional ready typings (#6073)
1 parent 60148c6 commit 4206e35

File tree

4 files changed

+46
-10
lines changed

4 files changed

+46
-10
lines changed

src/client/Client.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const VoiceRegion = require('../structures/VoiceRegion');
1818
const Webhook = require('../structures/Webhook');
1919
const Widget = require('../structures/Widget');
2020
const Collection = require('../util/Collection');
21-
const { Events, InviteScopes } = require('../util/Constants');
21+
const { Events, InviteScopes, Status } = require('../util/Constants');
2222
const DataResolver = require('../util/DataResolver');
2323
const Intents = require('../util/Intents');
2424
const Options = require('../util/Options');
@@ -227,6 +227,15 @@ class Client extends BaseClient {
227227
}
228228
}
229229

230+
/**
231+
* Returns whether the client has logged in, indicative of being able to access
232+
* properties such as `user` and `application`.
233+
* @returns {boolean}
234+
*/
235+
isReady() {
236+
return this.ws.status === Status.READY;
237+
}
238+
230239
/**
231240
* Logs out, terminates the connection to Discord, and destroys the client.
232241
* @returns {void}

src/client/websocket/WebSocketManager.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,9 @@ class WebSocketManager extends EventEmitter {
373373
/**
374374
* Emitted when the client becomes ready to start working.
375375
* @event Client#ready
376+
* @param {Client} client The client
376377
*/
377-
this.client.emit(Events.CLIENT_READY);
378+
this.client.emit(Events.CLIENT_READY, this.client);
378379

379380
this.handlePacket();
380381
}

typings/index.d.ts

+11-8
Original file line numberDiff line numberDiff line change
@@ -278,23 +278,25 @@ export class Channel extends Base {
278278
public toString(): ChannelMention;
279279
}
280280

281-
export class Client extends BaseClient {
281+
type If<T extends boolean, A, B = null> = T extends true ? A : T extends false ? B : A | B;
282+
283+
export class Client<Ready extends boolean = boolean> extends BaseClient {
282284
public constructor(options: ClientOptions);
283285
private actions: unknown;
284286
private _eval(script: string): unknown;
285287
private _validateOptions(options: ClientOptions): void;
286288

287-
public application: ClientApplication | null;
289+
public application: If<Ready, ClientApplication>;
288290
public channels: ChannelManager;
289291
public readonly emojis: BaseGuildEmojiManager;
290292
public guilds: GuildManager;
291293
public options: ClientOptions;
292-
public readyAt: Date | null;
293-
public readonly readyTimestamp: number | null;
294+
public readyAt: If<Ready, Date>;
295+
public readonly readyTimestamp: If<Ready, number>;
294296
public shard: ShardClientUtil | null;
295-
public token: string | null;
296-
public readonly uptime: number | null;
297-
public user: ClientUser | null;
297+
public token: If<Ready, string, string | null>;
298+
public uptime: If<Ready, number>;
299+
public user: If<Ready, ClientUser>;
298300
public users: UserManager;
299301
public voice: ClientVoiceManager;
300302
public ws: WebSocketManager;
@@ -307,6 +309,7 @@ export class Client extends BaseClient {
307309
public fetchWidget(guild: GuildResolvable): Promise<Widget>;
308310
public generateInvite(options?: InviteGenerationOptions): string;
309311
public login(token?: string): Promise<string>;
312+
public isReady(): this is Client<true>;
310313
public sweepMessages(lifetime?: number): number;
311314
public toJSON(): unknown;
312315

@@ -2818,7 +2821,7 @@ export interface ClientEvents {
28182821
presenceUpdate: [oldPresence: Presence | null, newPresence: Presence];
28192822
rateLimit: [rateLimitData: RateLimitData];
28202823
invalidRequestWarning: [invalidRequestWarningData: InvalidRequestWarningData];
2821-
ready: [];
2824+
ready: [client: Client<true>];
28222825
invalidated: [];
28232826
roleCreate: [role: Role];
28242827
roleDelete: [role: Role];

typings/index.ts

+23
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
ApplicationCommandResolvable,
66
CategoryChannel,
77
Client,
8+
ClientApplication,
9+
ClientUser,
810
Collection,
911
Constants,
1012
DMChannel,
@@ -447,6 +449,27 @@ client.on('interaction', async interaction => {
447449

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

452+
// Test client conditional types
453+
client.on('ready', client => {
454+
assertType<Client<true>>(client);
455+
});
456+
457+
declare const loggedInClient: Client<true>;
458+
assertType<ClientApplication>(loggedInClient.application);
459+
assertType<Date>(loggedInClient.readyAt);
460+
assertType<number>(loggedInClient.readyTimestamp);
461+
assertType<string>(loggedInClient.token);
462+
assertType<number>(loggedInClient.uptime);
463+
assertType<ClientUser>(loggedInClient.user);
464+
465+
declare const loggedOutClient: Client<false>;
466+
assertType<null>(loggedOutClient.application);
467+
assertType<null>(loggedOutClient.readyAt);
468+
assertType<null>(loggedOutClient.readyTimestamp);
469+
assertType<string | null>(loggedOutClient.token);
470+
assertType<null>(loggedOutClient.uptime);
471+
assertType<null>(loggedOutClient.user);
472+
450473
// Test type transformation:
451474
declare const assertType: <T>(value: T) => asserts value is T;
452475
declare const serialize: <T>(value: T) => Serialized<T>;

0 commit comments

Comments
 (0)