Skip to content

Commit 5c7cf19

Browse files
committed
refactor(rest): switch from undici request to global fetch
ci: bump node version test: remove console log refactor(Handler): .d.ts interface file refactor(Handler): move to interfaces folder chore: mark undici as normal dep feat: network request strategies refactor: default to request strategy docs: update to reflect recent changes refactor: performance improvements BREAKING CHANGE: NodeJS v18+ is required when using node due to the use of global `fetch` BREAKING CHANGE: The raw method of REST now returns a web compatible `Respone` object. BREAKING CHANGE: The `parseResponse` utility method has been updated to operate on a web compatible `Response` object. BREAKING CHANGE: Many underlying internals have changed, some of which were exported. BREAKING CHANGE: `DefaultRestOptions` used to contain a default `agent`, which is now set to `null` instead.
1 parent 8d5ce32 commit 5c7cf19

28 files changed

+315
-201
lines changed

packages/core/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
## Installation
2525

26-
**Node.js 16.9.0 or newer is required.**
26+
**Node.js 18.12.0 or newer is required.**
2727

2828
```sh
2929
npm install @discordjs/core

packages/core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"devDependencies": {
6565
"@favware/cliff-jumper": "^2.0.0",
6666
"@microsoft/api-extractor": "^7.34.8",
67-
"@types/node": "16.18.25",
67+
"@types/node": "18.15.11",
6868
"@vitest/coverage-c8": "^0.31.0",
6969
"cross-env": "^7.0.3",
7070
"esbuild-plugin-version-injector": "^1.1.0",
@@ -78,7 +78,7 @@
7878
"vitest": "^0.31.0"
7979
},
8080
"engines": {
81-
"node": ">=16.9.0"
81+
"node": ">=18.12.0"
8282
},
8383
"publishConfig": {
8484
"access": "public"

packages/discord.js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"@discordjs/builders": "workspace:^",
5454
"@discordjs/collection": "workspace:^",
5555
"@discordjs/formatters": "workspace:^",
56-
"@discordjs/rest": "workspace:^",
56+
"@discordjs/rest": "^1.7.1",
5757
"@discordjs/util": "workspace:^",
5858
"@discordjs/ws": "workspace:^",
5959
"@sapphire/snowflake": "^3.4.2",

packages/proxy-container/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"tslib": "^2.5.0"
5050
},
5151
"devDependencies": {
52-
"@types/node": "16.18.25",
52+
"@types/node": "18.15.11",
5353
"cross-env": "^7.0.3",
5454
"eslint": "^8.39.0",
5555
"eslint-config-neon": "^0.1.46",
@@ -60,7 +60,7 @@
6060
"typescript": "^5.0.4"
6161
},
6262
"engines": {
63-
"node": ">=16.9.0"
63+
"node": ">=18.12.0"
6464
},
6565
"publishConfig": {
6666
"access": "public"

packages/proxy/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
## Installation
2525

26-
**Node.js 16.9.0 or newer is required.**
26+
**Node.js 18.12.0 or newer is required.**
2727

2828
```sh
2929
npm install @discordjs/proxy

packages/proxy/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"devDependencies": {
6565
"@favware/cliff-jumper": "^2.0.0",
6666
"@microsoft/api-extractor": "^7.34.8",
67-
"@types/node": "16.18.25",
67+
"@types/node": "18.15.11",
6868
"@types/supertest": "^2.0.12",
6969
"@vitest/coverage-c8": "^0.31.0",
7070
"cross-env": "^7.0.3",
@@ -79,7 +79,7 @@
7979
"vitest": "^0.31.0"
8080
},
8181
"engines": {
82-
"node": ">=16.9.0"
82+
"node": ">=18.12.0"
8383
},
8484
"publishConfig": {
8585
"access": "public"

packages/proxy/src/util/responseHelpers.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
import type { ServerResponse } from 'node:http';
2+
import { Readable } from 'node:stream';
23
import { pipeline } from 'node:stream/promises';
3-
import { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest';
4-
import type { Dispatcher } from 'undici';
4+
import { DiscordAPIError, HTTPError, RateLimitError, type ResponseLike } from '@discordjs/rest';
55

66
/**
77
* Populates a server response with the data from a Discord 2xx REST response
88
*
99
* @param res - The server response to populate
1010
* @param data - The data to populate the response with
1111
*/
12-
export async function populateSuccessfulResponse(res: ServerResponse, data: Dispatcher.ResponseData): Promise<void> {
13-
res.statusCode = data.statusCode;
12+
export async function populateSuccessfulResponse(res: ServerResponse, data: ResponseLike): Promise<void> {
13+
res.statusCode = data.status;
1414

15-
for (const header of Object.keys(data.headers)) {
15+
for (const [header, value] of data.headers) {
1616
// Strip ratelimit headers
17-
if (header.startsWith('x-ratelimit')) {
17+
if (/^x-ratelimit/i.test(header)) {
1818
continue;
1919
}
2020

21-
res.setHeader(header, data.headers[header]!);
21+
res.setHeader(header, value);
2222
}
2323

24-
await pipeline(data.body, res);
24+
if (data.body) {
25+
await pipeline(data.body instanceof Readable ? data.body : Readable.fromWeb(data.body), res);
26+
}
2527
}
2628

2729
/**

packages/rest/README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
## Installation
2525

26-
**Node.js 16.9.0 or newer is required.**
26+
**Node.js 18.12.0 or newer is required.**
2727

2828
```sh
2929
npm install @discordjs/rest
@@ -80,6 +80,25 @@ try {
8080
}
8181
```
8282

83+
Send a basic message in an edge environment:
84+
85+
```js
86+
import { REST } from '@discordjs/rest';
87+
import { Routes } from 'discord-api-types/v10';
88+
89+
const rest = new REST({ version: '10', makeRequest: fetch }).setToken(TOKEN);
90+
91+
try {
92+
await rest.post(Routes.channelMessages(CHANNEL_ID), {
93+
body: {
94+
content: 'A message via REST from the edge!',
95+
},
96+
});
97+
} catch (error) {
98+
console.error(error);
99+
}
100+
```
101+
83102
## Links
84103

85104
- [Website][website] ([source][website-source])

packages/rest/__tests__/BurstHandler.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { performance } from 'node:perf_hooks';
44
import { MockAgent, setGlobalDispatcher } from 'undici';
55
import type { Interceptable, MockInterceptor } from 'undici/types/mock-interceptor';
6-
import { beforeEach, afterEach, test, expect, vitest } from 'vitest';
6+
import { beforeEach, afterEach, test, expect } from 'vitest';
77
import { DiscordAPIError, REST, BurstHandlerMajorIdKey } from '../src/index.js';
88
import { BurstHandler } from '../src/lib/handlers/BurstHandler.js';
99
import { genPath } from './util.js';
@@ -46,6 +46,7 @@ test('Interaction callback creates burst handler', async () => {
4646
auth: false,
4747
body: { type: 4, data: { content: 'Reply' } },
4848
}),
49+
// TODO: This should be ArrayBuffer, there is a bug in undici request
4950
).toBeInstanceOf(Uint8Array);
5051
expect(api.requestManager.handlers.get(callbackKey)).toBeInstanceOf(BurstHandler);
5152
});

packages/rest/__tests__/REST.test.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { URLSearchParams } from 'node:url';
33
import { DiscordSnowflake } from '@sapphire/snowflake';
44
import type { Snowflake } from 'discord-api-types/v10';
55
import { Routes } from 'discord-api-types/v10';
6-
import type { FormData } from 'undici';
6+
import { type FormData, fetch } from 'undici';
77
import { File as UndiciFile, MockAgent, setGlobalDispatcher } from 'undici';
88
import type { Interceptable, MockInterceptor } from 'undici/types/mock-interceptor.js';
9-
import { beforeEach, afterEach, test, expect } from 'vitest';
9+
import { beforeEach, afterEach, test, expect, vitest } from 'vitest';
1010
import { REST } from '../src/index.js';
1111
import { genPath } from './util.js';
1212

@@ -16,6 +16,10 @@ const newSnowflake: Snowflake = DiscordSnowflake.generate().toString();
1616

1717
const api = new REST().setToken('A-Very-Fake-Token');
1818

19+
const makeRequestMock = vitest.fn(fetch);
20+
21+
const fetchApi = new REST({ makeRequest: makeRequestMock }).setToken('A-Very-Fake-Token');
22+
1923
// @discordjs/rest uses the `content-type` header to detect whether to parse
2024
// the response as JSON or as an ArrayBuffer.
2125
const responseOptions: MockInterceptor.MockResponseOptions = {
@@ -114,6 +118,22 @@ test('simple POST', async () => {
114118
expect(await api.post('/simplePost')).toStrictEqual({ test: true });
115119
});
116120

121+
test('simple POST with fetch', async () => {
122+
mockPool
123+
.intercept({
124+
path: genPath('/fetchSimplePost'),
125+
method: 'POST',
126+
})
127+
.reply(() => ({
128+
data: { test: true },
129+
statusCode: 200,
130+
responseOptions,
131+
}));
132+
133+
expect(await fetchApi.post('/fetchSimplePost')).toStrictEqual({ test: true });
134+
expect(makeRequestMock).toHaveBeenCalledTimes(1);
135+
});
136+
117137
test('simple PUT 2', async () => {
118138
mockPool
119139
.intercept({
@@ -159,11 +179,11 @@ test('getAuth', async () => {
159179
path: genPath('/getAuth'),
160180
method: 'GET',
161181
})
162-
.reply((from) => ({
163-
data: { auth: (from.headers as unknown as Record<string, string | undefined>).Authorization ?? null },
164-
statusCode: 200,
182+
.reply(
183+
200,
184+
(from) => ({ auth: (from.headers as unknown as Record<string, string | undefined>).Authorization ?? null }),
165185
responseOptions,
166-
}))
186+
)
167187
.times(3);
168188

169189
// default
@@ -190,11 +210,13 @@ test('getReason', async () => {
190210
path: genPath('/getReason'),
191211
method: 'GET',
192212
})
193-
.reply((from) => ({
194-
data: { reason: (from.headers as unknown as Record<string, string | undefined>)['X-Audit-Log-Reason'] ?? null },
195-
statusCode: 200,
213+
.reply(
214+
200,
215+
(from) => ({
216+
reason: (from.headers as unknown as Record<string, string | undefined>)['X-Audit-Log-Reason'] ?? null,
217+
}),
196218
responseOptions,
197-
}))
219+
)
198220
.times(3);
199221

200222
// default

0 commit comments

Comments
 (0)