refactor: use interfaces for AsyncEventEmitter event maps (#10044)

* refactor: use interfaces for AsyncEventEmitter event maps

* refactor: apply suggestions from code review and add tests

* refactor: better errors on missing dispatch types
This commit is contained in:
Qjuh
2023-12-14 17:09:13 +01:00
committed by GitHub
parent f2138bb5a8
commit adfd9cd3b3
11 changed files with 102 additions and 66 deletions

View File

@@ -67,7 +67,7 @@
"homepage": "https://discord.js.org",
"dependencies": {
"@msgpack/msgpack": "^3.0.0-beta2",
"@vladfrangu/async_event_emitter": "^2.2.2",
"@vladfrangu/async_event_emitter": "^2.2.4",
"ioredis": "^5.3.2"
},
"devDependencies": {

View File

@@ -68,7 +68,7 @@
"@discordjs/util": "workspace:^",
"@discordjs/ws": "workspace:^",
"@sapphire/snowflake": "^3.5.1",
"@vladfrangu/async_event_emitter": "^2.2.2",
"@vladfrangu/async_event_emitter": "^2.2.4",
"discord-api-types": "0.37.61"
},
"devDependencies": {

View File

@@ -163,9 +163,7 @@ export interface MappedEvents {
[GatewayDispatchEvents.WebhooksUpdate]: [WithIntrinsicProps<GatewayWebhooksUpdateDispatchData>];
}
export type ManagerShardEventsMap = {
[K in keyof MappedEvents]: MappedEvents[K];
};
export interface ManagerShardEventsMap extends MappedEvents {}
export interface ClientOptions {
gateway: Gateway;
@@ -179,7 +177,7 @@ export interface RequestGuildMembersResult {
presences: NonNullable<GatewayGuildMembersChunkDispatchData['presences']>;
}
export class Client extends AsyncEventEmitter<ManagerShardEventsMap> {
export class Client extends AsyncEventEmitter<MappedEvents> {
public readonly rest: REST;
public readonly gateway: Gateway;
@@ -193,8 +191,12 @@ export class Client extends AsyncEventEmitter<ManagerShardEventsMap> {
this.api = new API(rest);
this.gateway.on(WebSocketShardEvents.Dispatch, ({ data: dispatch, shardId }) => {
// @ts-expect-error event props can't be resolved properly, but they are correct
this.emit(dispatch.t, this.wrapIntrinsicProps(dispatch.d, shardId));
this.emit(
// TODO: move this expect-error down to the next line once entitlements get merged, so missing dispatch types result in errors
// @ts-expect-error event props can't be resolved properly, but they are correct
dispatch.t,
this.wrapIntrinsicProps(dispatch.d, shardId),
);
});
}

View File

@@ -86,7 +86,7 @@
"@discordjs/util": "workspace:^",
"@sapphire/async-queue": "^1.5.0",
"@sapphire/snowflake": "^3.5.1",
"@vladfrangu/async_event_emitter": "^2.2.2",
"@vladfrangu/async_event_emitter": "^2.2.4",
"discord-api-types": "0.37.61",
"magic-bytes.js": "^1.5.0",
"tslib": "^2.6.2",

View File

@@ -18,7 +18,7 @@ import { RequestMethod } from './utils/types.js';
import type {
RESTOptions,
ResponseLike,
RestEventsMap,
RestEvents,
HashData,
InternalRequest,
RouteLike,
@@ -31,7 +31,7 @@ import { isBufferLike, parseResponse } from './utils/utils.js';
/**
* Represents the class that manages handlers for endpoints
*/
export class REST extends AsyncEventEmitter<RestEventsMap> {
export class REST extends AsyncEventEmitter<RestEvents> {
/**
* The {@link https://undici.nodejs.org/#/docs/api/Agent | Agent} for all requests
* performed by this manager.

View File

@@ -14,9 +14,7 @@ export interface RestEvents {
restDebug: [info: string];
}
export type RestEventsMap = {
[K in keyof RestEvents]: RestEvents[K];
};
export interface RestEventsMap extends RestEvents {}
/**
* Options to be passed when creating the REST instance

View File

@@ -0,0 +1,15 @@
import type { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
import { expectType, expectAssignable } from 'tsd';
import type { ManagerShardEventsMap, WebSocketShardEventsMap, WebSocketManager } from '../../src/index.js';
declare const manager: WebSocketManager;
declare const eventMap: ManagerShardEventsMap;
type AugmentedShardEventsMap = {
[K in keyof WebSocketShardEventsMap]: [
WebSocketShardEventsMap[K] extends [] ? { shardId: number } : WebSocketShardEventsMap[K][0] & { shardId: number },
];
};
expectType<AugmentedShardEventsMap>(eventMap);
expectAssignable<AsyncEventEmitter<AugmentedShardEventsMap>>(manager);

View File

@@ -77,7 +77,7 @@
"@discordjs/util": "workspace:^",
"@sapphire/async-queue": "^1.5.0",
"@types/ws": "^8.5.9",
"@vladfrangu/async_event_emitter": "^2.2.2",
"@vladfrangu/async_event_emitter": "^2.2.4",
"discord-api-types": "0.37.61",
"tslib": "^2.6.2",
"ws": "^8.14.2"
@@ -94,6 +94,7 @@
"eslint-formatter-pretty": "^5.0.0",
"mock-socket": "^9.3.1",
"prettier": "^3.1.0",
"tsd": "^0.29.0",
"tsup": "^7.2.0",
"turbo": "^1.10.17-canary.0",
"typescript": "^5.2.2",

View File

@@ -9,11 +9,13 @@ import {
type RESTGetAPIGatewayBotResult,
type GatewayIntentBits,
type GatewaySendPayload,
type GatewayDispatchPayload,
type GatewayReadyDispatchData,
} from 'discord-api-types/v10';
import type { IShardingStrategy } from '../strategies/sharding/IShardingStrategy.js';
import type { IIdentifyThrottler } from '../throttling/IIdentifyThrottler.js';
import { DefaultWebSocketManagerOptions, type CompressionMethod, type Encoding } from '../utils/constants.js';
import type { WebSocketShardDestroyOptions, WebSocketShardEventsMap } from './WebSocketShard.js';
import type { WebSocketShardDestroyOptions, WebSocketShardEvents } from './WebSocketShard.js';
/**
* Represents a range of shard ids
@@ -178,13 +180,24 @@ export interface OptionalWebSocketManagerOptions {
version: string;
}
export type WebSocketManagerOptions = OptionalWebSocketManagerOptions & RequiredWebSocketManagerOptions;
export interface WebSocketManagerOptions extends OptionalWebSocketManagerOptions, RequiredWebSocketManagerOptions {}
export type ManagerShardEventsMap = {
[K in keyof WebSocketShardEventsMap]: [
WebSocketShardEventsMap[K] extends [] ? { shardId: number } : WebSocketShardEventsMap[K][0] & { shardId: number },
export interface CreateWebSocketManagerOptions
extends Partial<OptionalWebSocketManagerOptions>,
RequiredWebSocketManagerOptions {}
export interface ManagerShardEventsMap {
[WebSocketShardEvents.Closed]: [{ code: number; shardId: number }];
[WebSocketShardEvents.Debug]: [payload: { message: string; shardId: number }];
[WebSocketShardEvents.Dispatch]: [payload: { data: GatewayDispatchPayload; shardId: number }];
[WebSocketShardEvents.Error]: [payload: { error: Error; shardId: number }];
[WebSocketShardEvents.Hello]: [{ shardId: number }];
[WebSocketShardEvents.Ready]: [payload: { data: GatewayReadyDispatchData; shardId: number }];
[WebSocketShardEvents.Resumed]: [{ shardId: number }];
[WebSocketShardEvents.HeartbeatComplete]: [
payload: { ackAt: number; heartbeatAt: number; latency: number; shardId: number },
];
};
}
export class WebSocketManager extends AsyncEventEmitter<ManagerShardEventsMap> {
/**
@@ -212,7 +225,7 @@ export class WebSocketManager extends AsyncEventEmitter<ManagerShardEventsMap> {
*/
private readonly strategy: IShardingStrategy;
public constructor(options: Partial<OptionalWebSocketManagerOptions> & RequiredWebSocketManagerOptions) {
public constructor(options: CreateWebSocketManagerOptions) {
super();
this.options = { ...DefaultWebSocketManagerOptions, ...options };
this.strategy = this.options.buildStrategy(this);

View File

@@ -52,8 +52,7 @@ export enum WebSocketShardDestroyRecovery {
Resume,
}
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type WebSocketShardEventsMap = {
export interface WebSocketShardEventsMap {
[WebSocketShardEvents.Closed]: [{ code: number }];
[WebSocketShardEvents.Debug]: [payload: { message: string }];
[WebSocketShardEvents.Dispatch]: [payload: { data: GatewayDispatchPayload }];
@@ -62,7 +61,7 @@ export type WebSocketShardEventsMap = {
[WebSocketShardEvents.Ready]: [payload: { data: GatewayReadyDispatchData }];
[WebSocketShardEvents.Resumed]: [];
[WebSocketShardEvents.HeartbeatComplete]: [payload: { ackAt: number; heartbeatAt: number; latency: number }];
};
}
export interface WebSocketShardDestroyOptions {
code?: number;

90
pnpm-lock.yaml generated
View File

@@ -607,8 +607,8 @@ importers:
specifier: ^3.0.0-beta2
version: 3.0.0-beta2
'@vladfrangu/async_event_emitter':
specifier: ^2.2.2
version: 2.2.2
specifier: ^2.2.4
version: 2.2.4
ioredis:
specifier: ^5.3.2
version: 5.3.2
@@ -786,8 +786,8 @@ importers:
specifier: ^3.5.1
version: 3.5.1
'@vladfrangu/async_event_emitter':
specifier: ^2.2.2
version: 2.2.2
specifier: ^2.2.4
version: 2.2.4
discord-api-types:
specifier: 0.37.61
version: 0.37.61
@@ -1280,8 +1280,8 @@ importers:
specifier: ^3.5.1
version: 3.5.1
'@vladfrangu/async_event_emitter':
specifier: ^2.2.2
version: 2.2.2
specifier: ^2.2.4
version: 2.2.4
discord-api-types:
specifier: 0.37.61
version: 0.37.61
@@ -1662,8 +1662,8 @@ importers:
specifier: ^8.5.9
version: 8.5.9
'@vladfrangu/async_event_emitter':
specifier: ^2.2.2
version: 2.2.2
specifier: ^2.2.4
version: 2.2.4
discord-api-types:
specifier: 0.37.61
version: 0.37.61
@@ -1707,6 +1707,9 @@ importers:
prettier:
specifier: ^3.1.0
version: 3.1.0
tsd:
specifier: ^0.29.0
version: 0.29.0
tsup:
specifier: ^7.2.0
version: 7.2.0(typescript@5.2.2)
@@ -3940,25 +3943,25 @@ packages:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@definitelytyped/header-parser@0.0.188:
resolution: {integrity: sha512-s2223ipUtaKy68u7Ku4QsB3ZmRLMKYGMlOrKALbG9dhkOF/zTO/jckcn1ZAU338oL0q+q5xDFMHja9qxs4TEaw==}
/@definitelytyped/header-parser@0.0.190:
resolution: {integrity: sha512-awWRynVpFt6uAVDzgOa1Ry0ttjQywtt4nh9wa3/MbSTEx6PNohL1X6xDjifUElLSTIUMDSAJyWO9FuKBjnX7IQ==}
engines: {node: '>=16.17.0'}
dependencies:
'@definitelytyped/typescript-versions': 0.0.181
'@definitelytyped/utils': 0.0.186
'@definitelytyped/typescript-versions': 0.0.182
'@definitelytyped/utils': 0.0.188
semver: 7.5.4
dev: true
/@definitelytyped/typescript-versions@0.0.181:
resolution: {integrity: sha512-D3iVQSPLNg8r9xissfcrBP0dsv9hsexz1z/KXBJ97fsqSLIGIiAzaDJJnY9NLzbAOzeazk6Ezetn8k0sIlXetg==}
/@definitelytyped/typescript-versions@0.0.182:
resolution: {integrity: sha512-ebGzGyZJW3ZSuE/nfAokKBo40HKnq/XvBbBnmCTR/3FCDX4aT7/6pQYEu2ihVI/2tf4+76GMoq0jRE69QWJ93g==}
engines: {node: '>=16.17.0'}
dev: true
/@definitelytyped/utils@0.0.186:
resolution: {integrity: sha512-T2e5QTVRFBP49B5yfk5kjbr3YYE2q1WqLkTTQNhhkJTTLoVFjhBSYF4PnWA0Op302aRiiCwDIuweqMmu3Zt8wg==}
/@definitelytyped/utils@0.0.188:
resolution: {integrity: sha512-NPUP1FvRbpac09qETtr1dw3Ri7Q07hp9WGOBjqhzXeXOSxfKs7c3BY6I+XJ2yxexG05LKrCKwgKRKgZlj+Zjzw==}
engines: {node: '>=16.17.0'}
dependencies:
'@definitelytyped/typescript-versions': 0.0.181
'@definitelytyped/typescript-versions': 0.0.182
'@qiwi/npm-registry-client': 8.9.1
'@types/node': 16.18.61
charm: 1.0.2
@@ -4430,7 +4433,7 @@ packages:
engines: {node: ^8.13.0 || >=10.10.0}
dependencies:
'@grpc/proto-loader': 0.7.10
'@types/node': 18.18.8
'@types/node': 18.18.9
dev: false
/@grpc/proto-loader@0.7.10:
@@ -4723,7 +4726,7 @@ packages:
dependencies:
'@types/istanbul-lib-coverage': 2.0.5
'@types/istanbul-reports': 3.0.3
'@types/node': 18.18.8
'@types/node': 18.18.9
'@types/yargs': 16.0.7
chalk: 4.1.2
dev: true
@@ -7182,7 +7185,7 @@ packages:
'@storybook/telemetry': 7.5.3
'@storybook/types': 7.5.3
'@types/detect-port': 1.3.4
'@types/node': 18.18.8
'@types/node': 18.18.9
'@types/pretty-hrtime': 1.0.2
'@types/semver': 7.5.5
better-opn: 3.0.2
@@ -7684,7 +7687,7 @@ packages:
resolution: {integrity: sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==}
dependencies:
'@types/connect': 3.4.37
'@types/node': 18.18.8
'@types/node': 18.18.9
dev: true
/@types/chai-subset@1.3.4:
@@ -7700,13 +7703,13 @@ packages:
/@types/concat-stream@2.0.1:
resolution: {integrity: sha512-v5HP9ZsRbzFq5XRo2liUZPKzwbGK5SuGVMWZjE6iJOm/JNdESk3/rkfcPe0lcal0C32PTLVlYUYqGpMGNdDsDg==}
dependencies:
'@types/node': 18.18.8
'@types/node': 18.18.9
dev: true
/@types/connect@3.4.37:
resolution: {integrity: sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==}
dependencies:
'@types/node': 18.18.8
'@types/node': 18.18.9
dev: true
/@types/cookiejar@2.1.4:
@@ -7716,7 +7719,7 @@ packages:
/@types/cross-spawn@6.0.4:
resolution: {integrity: sha512-GGLpeThc2Bu8FBGmVn76ZU3lix17qZensEI4/MPty0aZpm2CHfgEMis31pf5X5EiudYKcPAsWciAsCALoPo5dw==}
dependencies:
'@types/node': 18.18.8
'@types/node': 18.18.9
dev: true
/@types/debug@4.1.10:
@@ -7777,7 +7780,7 @@ packages:
/@types/express-serve-static-core@4.17.40:
resolution: {integrity: sha512-dzQWNQktgK3AyMpPeIeWbnR/ve2wU0bDSfdhf+RSt1ivelrO3hwfrKjTZvJDK4IyGWlDoRj+knNSePnL7OUqOA==}
dependencies:
'@types/node': 18.18.8
'@types/node': 18.18.9
'@types/qs': 6.9.9
'@types/range-parser': 1.2.6
'@types/send': 0.17.3
@@ -7800,7 +7803,7 @@ packages:
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 18.18.8
'@types/node': 18.18.9
dev: true
/@types/graceful-fs@4.1.8:
@@ -7973,6 +7976,7 @@ packages:
resolution: {integrity: sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==}
dependencies:
undici-types: 5.26.5
dev: true
/@types/node@18.18.9:
resolution: {integrity: sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==}
@@ -8048,7 +8052,7 @@ packages:
resolution: {integrity: sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==}
dependencies:
'@types/mime': 1.3.4
'@types/node': 18.18.8
'@types/node': 18.18.9
dev: true
/@types/serve-static@1.15.4:
@@ -8056,7 +8060,7 @@ packages:
dependencies:
'@types/http-errors': 2.0.3
'@types/mime': 3.0.3
'@types/node': 18.18.8
'@types/node': 18.18.9
dev: true
/@types/stack-utils@2.0.2:
@@ -8082,7 +8086,7 @@ packages:
/@types/through@0.0.32:
resolution: {integrity: sha512-7XsfXIsjdfJM2wFDRAtEWp3zb2aVPk5QeyZxGlVK57q4u26DczMHhJmlhr0Jqv0THwxam/L8REXkj8M2I/lcvw==}
dependencies:
'@types/node': 18.18.8
'@types/node': 18.18.9
dev: true
/@types/tinycolor2@1.4.5:
@@ -9073,8 +9077,8 @@ packages:
pretty-format: 29.7.0
dev: true
/@vladfrangu/async_event_emitter@2.2.2:
resolution: {integrity: sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==}
/@vladfrangu/async_event_emitter@2.2.4:
resolution: {integrity: sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
dev: false
@@ -10606,6 +10610,7 @@ packages:
engines: {node: '>=0.10.0'}
requiresBuild: true
dev: true
optional: true
/collect-all@1.0.4:
resolution: {integrity: sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==}
@@ -11401,7 +11406,7 @@ packages:
supports-color:
optional: true
dependencies:
ms: 2.1.1
ms: 2.1.3
dev: true
/debug@4.3.4:
@@ -11781,7 +11786,7 @@ packages:
dependencies:
semver: 7.5.4
shelljs: 0.8.5
typescript: 5.4.0-dev.20231113
typescript: 5.4.0-dev.20231211
dev: true
/dts-critic@3.3.11(typescript@5.2.2):
@@ -11793,7 +11798,7 @@ packages:
typescript:
optional: true
dependencies:
'@definitelytyped/header-parser': 0.0.188
'@definitelytyped/header-parser': 0.0.190
command-exists: 1.2.9
rimraf: 3.0.2
semver: 6.3.1
@@ -11812,9 +11817,9 @@ packages:
typescript:
optional: true
dependencies:
'@definitelytyped/header-parser': 0.0.188
'@definitelytyped/typescript-versions': 0.0.181
'@definitelytyped/utils': 0.0.186
'@definitelytyped/header-parser': 0.0.190
'@definitelytyped/typescript-versions': 0.0.182
'@definitelytyped/utils': 0.0.188
dts-critic: 3.3.11(typescript@5.2.2)
fs-extra: 6.0.1
json-stable-stringify: 1.0.2
@@ -14944,6 +14949,7 @@ packages:
dependencies:
number-is-nan: 1.0.1
dev: true
optional: true
/is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
@@ -17969,6 +17975,7 @@ packages:
engines: {node: '>=0.10.0'}
requiresBuild: true
dev: true
optional: true
/nwsapi@2.2.7:
resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==}
@@ -18903,7 +18910,7 @@ packages:
'@protobufjs/path': 1.1.2
'@protobufjs/pool': 1.1.0
'@protobufjs/utf8': 1.1.0
'@types/node': 18.18.8
'@types/node': 18.18.9
long: 5.2.3
dev: false
@@ -20571,6 +20578,7 @@ packages:
is-fullwidth-code-point: 1.0.0
strip-ansi: 3.0.1
dev: true
optional: true
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
@@ -21749,8 +21757,8 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
/typescript@5.4.0-dev.20231113:
resolution: {integrity: sha512-5K165L/tImARYZDKwwT2ER9qKt0n56E8jxldXfAVpq8qNqX5o2SvpoPrzCi+eddkHJHl1gPf26xiE+7R6//1Gg==}
/typescript@5.4.0-dev.20231211:
resolution: {integrity: sha512-QbX1BYW7kMO+l74HzSfmOSGl66t/DmtqnNRcaUTeowtc/SX/v7WCV3jpuaHgAIS0eDKorcRoRizmWrxlvBOeUQ==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
@@ -22859,7 +22867,7 @@ packages:
/wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
dependencies:
string-width: 1.0.2
string-width: 4.2.3
dev: true
/wordwrap@1.0.0: