@clickup/rest-client 2.10.296 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.base.js +10 -1
- package/.eslintrc.js +4 -4
- package/.github/workflows/ci.yml +26 -0
- package/.github/workflows/semgrep.yml +36 -0
- package/.prettierrc +8 -0
- package/.vscode/extensions.json +8 -0
- package/.vscode/tasks.json +20 -0
- package/babel.config.js +5 -0
- package/dist/.eslintcache +1 -1
- package/dist/RestClient.d.ts +1 -3
- package/dist/RestClient.d.ts.map +1 -1
- package/dist/RestClient.js +3 -4
- package/dist/RestClient.js.map +1 -1
- package/dist/RestOptions.d.ts +2 -2
- package/dist/RestOptions.d.ts.map +1 -1
- package/dist/RestOptions.js +1 -0
- package/dist/RestOptions.js.map +1 -1
- package/dist/RestRequest.d.ts +0 -2
- package/dist/RestRequest.d.ts.map +1 -1
- package/dist/RestRequest.js +12 -5
- package/dist/RestRequest.js.map +1 -1
- package/dist/RestResponse.d.ts +0 -1
- package/dist/RestResponse.d.ts.map +1 -1
- package/dist/helpers/depaginate.js +1 -1
- package/dist/helpers/depaginate.js.map +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/internal/RestFetchReader.d.ts +3 -3
- package/dist/internal/RestFetchReader.d.ts.map +1 -1
- package/dist/internal/RestFetchReader.js +21 -11
- package/dist/internal/RestFetchReader.js.map +1 -1
- package/dist/internal/RestRangeUploader.d.ts +0 -1
- package/dist/internal/RestRangeUploader.d.ts.map +1 -1
- package/dist/internal/calcRetryDelay.js +1 -1
- package/dist/internal/calcRetryDelay.js.map +1 -1
- package/dist/internal/ellipsis.d.ts +6 -0
- package/dist/internal/ellipsis.d.ts.map +1 -0
- package/dist/internal/ellipsis.js +17 -0
- package/dist/internal/ellipsis.js.map +1 -0
- package/dist/internal/inferResBodyEncoding.d.ts +0 -1
- package/dist/internal/inferResBodyEncoding.d.ts.map +1 -1
- package/dist/internal/inferResBodyEncoding.js +1 -1
- package/dist/internal/inferResBodyEncoding.js.map +1 -1
- package/dist/internal/inspectPossibleJSON.d.ts +0 -2
- package/dist/internal/inspectPossibleJSON.d.ts.map +1 -1
- package/dist/internal/inspectPossibleJSON.js +6 -9
- package/dist/internal/inspectPossibleJSON.js.map +1 -1
- package/dist/internal/prependNewlineIfMultiline.js +1 -1
- package/dist/internal/prependNewlineIfMultiline.js.map +1 -1
- package/dist/internal/substituteParams.js +1 -1
- package/dist/internal/substituteParams.js.map +1 -1
- package/dist/internal/throwIfErrorResponse.js +2 -2
- package/dist/internal/throwIfErrorResponse.js.map +1 -1
- package/dist/internal/toFloatMs.js +1 -1
- package/dist/internal/toFloatMs.js.map +1 -1
- package/dist/middlewares/paceRequests.d.ts +21 -2
- package/dist/middlewares/paceRequests.d.ts.map +1 -1
- package/dist/middlewares/paceRequests.js +4 -3
- package/dist/middlewares/paceRequests.js.map +1 -1
- package/docs/interfaces/Pacer.md +7 -12
- package/docs/interfaces/PacerOutcome.md +25 -0
- package/docs/interfaces/RestOptions.md +22 -47
- package/docs/modules.md +4 -7
- package/internal/clean.sh +4 -0
- package/internal/deploy.sh +7 -0
- package/internal/docs.sh +6 -0
- package/internal/lint.sh +4 -0
- package/jest.config.base.js +18 -0
- package/jest.config.js +1 -10
- package/package.json +15 -10
- package/src/RestClient.ts +2 -2
- package/src/RestOptions.ts +3 -0
- package/src/RestRequest.ts +32 -5
- package/src/__tests__/RestClient.test.ts +53 -0
- package/src/__tests__/RestFetchReader.test.ts +154 -0
- package/src/__tests__/RestRequest.test.ts +262 -0
- package/src/__tests__/RestRequestCacheableLookup.test.ts +88 -0
- package/src/__tests__/RestStream.test.ts +67 -0
- package/src/__tests__/helpers.ts +173 -0
- package/src/index.ts +2 -7
- package/src/internal/RestFetchReader.ts +4 -1
- package/src/internal/ellipsis.ts +16 -0
- package/src/internal/inspectPossibleJSON.ts +1 -5
- package/src/internal/throwIfErrorResponse.ts +1 -1
- package/src/middlewares/paceRequests.ts +27 -2
- package/tsconfig.base.json +31 -0
- package/tsconfig.json +1 -31
- package/typedoc.config.js +20 -0
- package/dist/pacers/Pacer.d.ts +0 -21
- package/dist/pacers/Pacer.d.ts.map +0 -1
- package/dist/pacers/Pacer.js +0 -3
- package/dist/pacers/Pacer.js.map +0 -1
- package/dist/pacers/PacerComposite.d.ts +0 -14
- package/dist/pacers/PacerComposite.d.ts.map +0 -1
- package/dist/pacers/PacerComposite.js +0 -32
- package/dist/pacers/PacerComposite.js.map +0 -1
- package/dist/pacers/PacerQPS.d.ts +0 -53
- package/dist/pacers/PacerQPS.d.ts.map +0 -1
- package/dist/pacers/PacerQPS.js +0 -105
- package/dist/pacers/PacerQPS.js.map +0 -1
- package/docs/classes/PacerComposite.md +0 -66
- package/docs/classes/PacerQPS.md +0 -79
- package/docs/interfaces/PacerDelay.md +0 -25
- package/docs/interfaces/PacerQPSBackend.md +0 -44
- package/docs/interfaces/PacerQPSOptions.md +0 -40
- package/src/pacers/Pacer.ts +0 -22
- package/src/pacers/PacerComposite.ts +0 -29
- package/src/pacers/PacerQPS.ts +0 -147
- package/typedoc.json +0 -22
|
@@ -126,6 +126,18 @@ addresses are allowed.
|
|
|
126
126
|
|
|
127
127
|
___
|
|
128
128
|
|
|
129
|
+
### responseEncoding
|
|
130
|
+
|
|
131
|
+
• **responseEncoding**: `undefined` \| `BufferEncoding`
|
|
132
|
+
|
|
133
|
+
Overrides the default encoding heuristics for responses.
|
|
134
|
+
|
|
135
|
+
#### Defined in
|
|
136
|
+
|
|
137
|
+
[src/RestOptions.ts:100](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L100)
|
|
138
|
+
|
|
139
|
+
___
|
|
140
|
+
|
|
129
141
|
### isDebug
|
|
130
142
|
|
|
131
143
|
• **isDebug**: `boolean`
|
|
@@ -134,7 +146,7 @@ If true, logs request-response pairs to console.
|
|
|
134
146
|
|
|
135
147
|
#### Defined in
|
|
136
148
|
|
|
137
|
-
[src/RestOptions.ts:
|
|
149
|
+
[src/RestOptions.ts:102](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L102)
|
|
138
150
|
|
|
139
151
|
___
|
|
140
152
|
|
|
@@ -153,7 +165,7 @@ Sets Keep-Alive parameters (persistent connections).
|
|
|
153
165
|
|
|
154
166
|
#### Defined in
|
|
155
167
|
|
|
156
|
-
[src/RestOptions.ts:
|
|
168
|
+
[src/RestOptions.ts:106](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L106)
|
|
157
169
|
|
|
158
170
|
___
|
|
159
171
|
|
|
@@ -165,7 +177,7 @@ When resolving DNS, use IPv4, IPv6 or both (see dns.lookup() docs).
|
|
|
165
177
|
|
|
166
178
|
#### Defined in
|
|
167
179
|
|
|
168
|
-
[src/RestOptions.ts:
|
|
180
|
+
[src/RestOptions.ts:114](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L114)
|
|
169
181
|
|
|
170
182
|
___
|
|
171
183
|
|
|
@@ -177,7 +189,7 @@ Max timeout to wait for a response.
|
|
|
177
189
|
|
|
178
190
|
#### Defined in
|
|
179
191
|
|
|
180
|
-
[src/RestOptions.ts:
|
|
192
|
+
[src/RestOptions.ts:116](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L116)
|
|
181
193
|
|
|
182
194
|
___
|
|
183
195
|
|
|
@@ -192,9 +204,6 @@ delay events logging.
|
|
|
192
204
|
|
|
193
205
|
▸ (`event`): `void`
|
|
194
206
|
|
|
195
|
-
Logger to be used for each responses (including retried) plus for backoff
|
|
196
|
-
delay events logging.
|
|
197
|
-
|
|
198
207
|
##### Parameters
|
|
199
208
|
|
|
200
209
|
| Name | Type |
|
|
@@ -207,7 +216,7 @@ delay events logging.
|
|
|
207
216
|
|
|
208
217
|
#### Defined in
|
|
209
218
|
|
|
210
|
-
[src/RestOptions.ts:
|
|
219
|
+
[src/RestOptions.ts:119](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L119)
|
|
211
220
|
|
|
212
221
|
___
|
|
213
222
|
|
|
@@ -219,7 +228,7 @@ Middlewares to wrap requests. May alter both request and response.
|
|
|
219
228
|
|
|
220
229
|
#### Defined in
|
|
221
230
|
|
|
222
|
-
[src/RestOptions.ts:
|
|
231
|
+
[src/RestOptions.ts:121](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L121)
|
|
223
232
|
|
|
224
233
|
___
|
|
225
234
|
|
|
@@ -244,19 +253,6 @@ remote API is that weird. Return values:
|
|
|
244
253
|
|
|
245
254
|
▸ (`res`): ``"SUCCESS"`` \| ``"THROW"`` \| ``"BEST_EFFORT"``
|
|
246
255
|
|
|
247
|
-
If set, makes decision whether the response is successful or not. The
|
|
248
|
-
response will either be returned to the client, or an error will be thrown.
|
|
249
|
-
This allows to treat some non-successful HTTP statuses as success if the
|
|
250
|
-
remote API is that weird. Return values:
|
|
251
|
-
* "SUCCESS" - the request will be considered successful, no further checks
|
|
252
|
-
will be performed;
|
|
253
|
-
* "BEST_EFFORT" - inconclusive, the request may be either successful or
|
|
254
|
-
unsuccessful, additional tests (e.g. will check HTTP status code) will be
|
|
255
|
-
performed;
|
|
256
|
-
* "THROW" - the request resulted in error. Additional tests will be
|
|
257
|
-
performed to determine is the error is retriable, is OAuth token good,
|
|
258
|
-
and etc.
|
|
259
|
-
|
|
260
256
|
##### Parameters
|
|
261
257
|
|
|
262
258
|
| Name | Type |
|
|
@@ -269,7 +265,7 @@ remote API is that weird. Return values:
|
|
|
269
265
|
|
|
270
266
|
#### Defined in
|
|
271
267
|
|
|
272
|
-
[src/RestOptions.ts:
|
|
268
|
+
[src/RestOptions.ts:134](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L134)
|
|
273
269
|
|
|
274
270
|
___
|
|
275
271
|
|
|
@@ -291,16 +287,6 @@ contradictory information; then isRateLimitError wins.
|
|
|
291
287
|
|
|
292
288
|
▸ (`res`): `number` \| ``"BEST_EFFORT"`` \| ``"SOMETHING_ELSE"`` \| ``"RATE_LIMIT"``
|
|
293
289
|
|
|
294
|
-
Decides whether the response is a rate-limit error or not. Returning
|
|
295
|
-
non-zero value is treated as retry delay (if retries are set up). In case
|
|
296
|
-
the returned value is "SOMETHING_ELSE", the response ought to be either
|
|
297
|
-
success or some other error. Returning "BEST_EFFORT" turns on built-in
|
|
298
|
-
heuristic (e.g. relying on HTTP status code and Retry-After header). In
|
|
299
|
-
case we've made a decision that it's a rate limited error, the request is
|
|
300
|
-
always retried; this covers a very common case when we have both
|
|
301
|
-
isRateLimitError and isRetriableError handlers set up, and they return
|
|
302
|
-
contradictory information; then isRateLimitError wins.
|
|
303
|
-
|
|
304
290
|
##### Parameters
|
|
305
291
|
|
|
306
292
|
| Name | Type |
|
|
@@ -313,7 +299,7 @@ contradictory information; then isRateLimitError wins.
|
|
|
313
299
|
|
|
314
300
|
#### Defined in
|
|
315
301
|
|
|
316
|
-
[src/RestOptions.ts:
|
|
302
|
+
[src/RestOptions.ts:144](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L144)
|
|
317
303
|
|
|
318
304
|
___
|
|
319
305
|
|
|
@@ -328,9 +314,6 @@ not, the response ought to be either success or some other error.
|
|
|
328
314
|
|
|
329
315
|
▸ (`res`): `boolean`
|
|
330
316
|
|
|
331
|
-
Decides whether the response is a token-invalid error or not. In case it's
|
|
332
|
-
not, the response ought to be either success or some other error.
|
|
333
|
-
|
|
334
317
|
##### Parameters
|
|
335
318
|
|
|
336
319
|
| Name | Type |
|
|
@@ -343,7 +326,7 @@ not, the response ought to be either success or some other error.
|
|
|
343
326
|
|
|
344
327
|
#### Defined in
|
|
345
328
|
|
|
346
|
-
[src/RestOptions.ts:
|
|
329
|
+
[src/RestOptions.ts:149](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L149)
|
|
347
330
|
|
|
348
331
|
___
|
|
349
332
|
|
|
@@ -363,14 +346,6 @@ retry will happen in not less than this number of milliseconds.
|
|
|
363
346
|
|
|
364
347
|
▸ (`res`, `_error`): `number` \| ``"BEST_EFFORT"`` \| ``"NEVER_RETRY"`` \| ``"RETRY"``
|
|
365
348
|
|
|
366
|
-
Called only if we haven't decided earlier that it's a rate limit error.
|
|
367
|
-
Decides whether the response is a retriable error or not. In case the
|
|
368
|
-
returned value is "NEVER_RETRY", the response ought to be either success or
|
|
369
|
-
some other error, but it's guaranteed that the request won't be retried.
|
|
370
|
-
Returning "BEST_EFFORT" turns on built-in heuristics (e.g. never retry "not
|
|
371
|
-
found" errors). Returning a number is treated as "RETRY", and the next
|
|
372
|
-
retry will happen in not less than this number of milliseconds.
|
|
373
|
-
|
|
374
349
|
##### Parameters
|
|
375
350
|
|
|
376
351
|
| Name | Type |
|
|
@@ -384,4 +359,4 @@ retry will happen in not less than this number of milliseconds.
|
|
|
384
359
|
|
|
385
360
|
#### Defined in
|
|
386
361
|
|
|
387
|
-
[src/RestOptions.ts:
|
|
362
|
+
[src/RestOptions.ts:157](https://github.com/clickup/rest-client/blob/master/src/RestOptions.ts#L157)
|
package/docs/modules.md
CHANGED
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
- [RestRetriableError](classes/RestRetriableError.md)
|
|
16
16
|
- [RestTimeoutError](classes/RestTimeoutError.md)
|
|
17
17
|
- [RestTokenInvalidError](classes/RestTokenInvalidError.md)
|
|
18
|
-
- [PacerComposite](classes/PacerComposite.md)
|
|
19
|
-
- [PacerQPS](classes/PacerQPS.md)
|
|
20
18
|
|
|
21
19
|
## Interfaces
|
|
22
20
|
|
|
@@ -24,10 +22,8 @@
|
|
|
24
22
|
- [RestLogEvent](interfaces/RestLogEvent.md)
|
|
25
23
|
- [Middleware](interfaces/Middleware.md)
|
|
26
24
|
- [RestOptions](interfaces/RestOptions.md)
|
|
27
|
-
- [PacerDelay](interfaces/PacerDelay.md)
|
|
28
25
|
- [Pacer](interfaces/Pacer.md)
|
|
29
|
-
- [
|
|
30
|
-
- [PacerQPSOptions](interfaces/PacerQPSOptions.md)
|
|
26
|
+
- [PacerOutcome](interfaces/PacerOutcome.md)
|
|
31
27
|
|
|
32
28
|
## Functions
|
|
33
29
|
|
|
@@ -67,7 +63,7 @@ ___
|
|
|
67
63
|
|
|
68
64
|
### paceRequests
|
|
69
65
|
|
|
70
|
-
▸ **paceRequests**(`pacer`): [`Middleware`](interfaces/Middleware.md)
|
|
66
|
+
▸ **paceRequests**(`pacer`, `delayMetric?`): [`Middleware`](interfaces/Middleware.md)
|
|
71
67
|
|
|
72
68
|
Rest Client middleware that adds some delay between requests using one of
|
|
73
69
|
Pacer implementations.
|
|
@@ -77,6 +73,7 @@ Pacer implementations.
|
|
|
77
73
|
| Name | Type |
|
|
78
74
|
| :------ | :------ |
|
|
79
75
|
| `pacer` | ``null`` \| [`Pacer`](interfaces/Pacer.md) \| (`req`: [`RestRequest`](classes/RestRequest.md)\<`any`\>) => `Promise`\<``null`` \| [`Pacer`](interfaces/Pacer.md)\> |
|
|
76
|
+
| `delayMetric?` | (`delay`: `number`, `reason`: `string`) => `void` |
|
|
80
77
|
|
|
81
78
|
#### Returns
|
|
82
79
|
|
|
@@ -84,4 +81,4 @@ Pacer implementations.
|
|
|
84
81
|
|
|
85
82
|
#### Defined in
|
|
86
83
|
|
|
87
|
-
[src/middlewares/paceRequests.ts:
|
|
84
|
+
[src/middlewares/paceRequests.ts:33](https://github.com/clickup/rest-client/blob/master/src/middlewares/paceRequests.ts#L33)
|
package/internal/docs.sh
ADDED
package/internal/lint.sh
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
module.exports = {
|
|
3
|
+
roots: ["<rootDir>/src"],
|
|
4
|
+
testMatch: ["**/*.test.ts"],
|
|
5
|
+
clearMocks: true,
|
|
6
|
+
restoreMocks: true,
|
|
7
|
+
...(process.env.IN_JEST_PROJECT
|
|
8
|
+
? {}
|
|
9
|
+
: { testTimeout: 30000 }),
|
|
10
|
+
transform: {
|
|
11
|
+
"\\.ts$": "ts-jest",
|
|
12
|
+
"\\.m?js$": "babel-jest",
|
|
13
|
+
},
|
|
14
|
+
transformIgnorePatterns: [
|
|
15
|
+
"<rootDir>/node_modules/.pnpm/(?!(cacheable-lookup)@)",
|
|
16
|
+
"node_modules/(?!.pnpm|cacheable-lookup)",
|
|
17
|
+
],
|
|
18
|
+
};
|
package/jest.config.js
CHANGED
|
@@ -1,11 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
module.exports = {
|
|
3
|
-
roots: ["<rootDir>/src"],
|
|
4
|
-
testMatch: ["**/*.test.ts"],
|
|
5
|
-
clearMocks: true,
|
|
6
|
-
restoreMocks: true,
|
|
7
|
-
...(process.env.IN_JEST_PROJECT ? {} : { forceExit: true }),
|
|
8
|
-
transform: {
|
|
9
|
-
"\\.ts$": "ts-jest",
|
|
10
|
-
},
|
|
11
|
-
};
|
|
2
|
+
module.exports = { ...require("./jest.config.base") };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clickup/rest-client",
|
|
3
3
|
"description": "A syntax sugar tool around Node fetch() API, tailored to work with TypeScript and response validators",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.11.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"rest-client",
|
|
@@ -24,47 +24,52 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc",
|
|
26
26
|
"dev": "tsc --watch --preserveWatchOutput",
|
|
27
|
-
"lint": "
|
|
27
|
+
"lint": "bash internal/lint.sh",
|
|
28
28
|
"test": "jest",
|
|
29
|
-
"docs": "
|
|
30
|
-
"clean": "
|
|
29
|
+
"docs": "bash internal/docs.sh",
|
|
30
|
+
"clean": "bash internal/clean.sh",
|
|
31
31
|
"copy-package-to-public-dir": "copy-package-to-public-dir.sh",
|
|
32
32
|
"backport-package-from-public-dir": "backport-package-from-public-dir.sh",
|
|
33
|
-
"deploy": "
|
|
33
|
+
"deploy": "bash internal/deploy.sh"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"abort-controller": "^3.0.0",
|
|
37
|
+
"cacheable-lookup": "^7.0.0",
|
|
37
38
|
"delay": "^4.4.1",
|
|
38
|
-
"fast-typescript-memoize": "^1.
|
|
39
|
+
"fast-typescript-memoize": "^1.1.1",
|
|
39
40
|
"ipaddr.js": "^1.9.1",
|
|
40
41
|
"lodash": "^4.17.21",
|
|
41
42
|
"node-fetch": "^2.6.11",
|
|
42
43
|
"oauth-1.0a": "^2.2.6"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
46
|
+
"@babel/core": "^7.29.0",
|
|
47
|
+
"@babel/preset-env": "^7.29.0",
|
|
45
48
|
"@types/jest": "^29.5.5",
|
|
46
49
|
"@types/lodash": "^4.14.175",
|
|
47
|
-
"@types/node-fetch": "^2.6.4",
|
|
48
50
|
"@types/node": "^20.4.1",
|
|
51
|
+
"@types/node-fetch": "^2.6.4",
|
|
49
52
|
"@typescript-eslint/eslint-plugin": "^5.59.6",
|
|
50
53
|
"@typescript-eslint/parser": "^5.59.6",
|
|
54
|
+
"babel-jest": "^29.7.0",
|
|
55
|
+
"eslint": "^8.40.0",
|
|
51
56
|
"eslint-import-resolver-typescript": "^3.5.5",
|
|
52
57
|
"eslint-plugin-import": "^2.27.5",
|
|
53
58
|
"eslint-plugin-lodash": "^7.4.0",
|
|
59
|
+
"eslint-plugin-no-only-tests": "^3.1.0",
|
|
54
60
|
"eslint-plugin-node": "^11.1.0",
|
|
55
|
-
"eslint-plugin-react-hooks": "^4.6.0",
|
|
56
61
|
"eslint-plugin-react": "^7.32.2",
|
|
62
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
57
63
|
"eslint-plugin-typescript-enum": "^2.1.0",
|
|
58
64
|
"eslint-plugin-typescript-sort-keys": "^2.3.0",
|
|
59
65
|
"eslint-plugin-unused-imports": "^2.0.0",
|
|
60
|
-
"eslint": "^8.40.0",
|
|
61
66
|
"jest": "^29.7.0",
|
|
62
67
|
"prettier": "3.2.1",
|
|
63
68
|
"superstruct": "^1.0.3",
|
|
64
69
|
"ts-jest": "^29.1.1",
|
|
70
|
+
"typedoc": "^0.25.2",
|
|
65
71
|
"typedoc-plugin-markdown": "^3.16.0",
|
|
66
72
|
"typedoc-plugin-merge-modules": "^5.1.0",
|
|
67
|
-
"typedoc": "^0.25.2",
|
|
68
73
|
"typescript": "^5.2.2"
|
|
69
74
|
},
|
|
70
75
|
"repository": {
|
package/src/RestClient.ts
CHANGED
|
@@ -480,8 +480,8 @@ function simpleShape(path: string, args?: any) {
|
|
|
480
480
|
args && typeof args === "object"
|
|
481
481
|
? Object.keys(args)
|
|
482
482
|
// Filter out args that are already mentioned in the path, e.g.
|
|
483
|
-
// /pages/:pageID/blocks.
|
|
484
|
-
.filter((arg) => !argsInPath.includes(arg))
|
|
483
|
+
// /pages/:pageID/blocks. Also filter out undefined args.
|
|
484
|
+
.filter((arg) => !argsInPath.includes(arg) && args[arg] !== undefined)
|
|
485
485
|
.sort()
|
|
486
486
|
.join(",")
|
|
487
487
|
: "";
|
package/src/RestOptions.ts
CHANGED
|
@@ -96,6 +96,8 @@ export default interface RestOptions {
|
|
|
96
96
|
/** If true, non-public IP addresses are allowed too; otherwise, only unicast
|
|
97
97
|
* addresses are allowed. */
|
|
98
98
|
allowInternalIPs: boolean;
|
|
99
|
+
/** Overrides the default encoding heuristics for responses. */
|
|
100
|
+
responseEncoding: NodeJS.BufferEncoding | undefined;
|
|
99
101
|
/** If true, logs request-response pairs to console. */
|
|
100
102
|
isDebug: boolean;
|
|
101
103
|
/** @ignore Holds HttpsAgent/HttpAgent instances; used internally only. */
|
|
@@ -172,6 +174,7 @@ export const DEFAULT_OPTIONS: RestOptions = {
|
|
|
172
174
|
throwIfResIsBigger: undefined,
|
|
173
175
|
privateDataInResponse: false,
|
|
174
176
|
allowInternalIPs: false,
|
|
177
|
+
responseEncoding: undefined,
|
|
175
178
|
isDebug: false,
|
|
176
179
|
agents: new Agents(),
|
|
177
180
|
keepAlive: { timeoutMs: 10000 },
|
package/src/RestRequest.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import dns from "dns";
|
|
2
|
-
import { promisify } from "util";
|
|
3
1
|
import { Memoize } from "fast-typescript-memoize";
|
|
4
2
|
import { parse } from "ipaddr.js";
|
|
5
3
|
import random from "lodash/random";
|
|
@@ -20,6 +18,36 @@ import RestStream from "./RestStream";
|
|
|
20
18
|
|
|
21
19
|
const MAX_DEBUG_LEN = 1024 * 100;
|
|
22
20
|
|
|
21
|
+
interface CacheableLookupEntry {
|
|
22
|
+
address: string;
|
|
23
|
+
family: 4 | 6;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface CacheableLookupLike {
|
|
27
|
+
lookupAsync(hostname: string): Promise<CacheableLookupEntry>;
|
|
28
|
+
lookupAsync(
|
|
29
|
+
hostname: string,
|
|
30
|
+
options: { family: 4 | 6 },
|
|
31
|
+
): Promise<CacheableLookupEntry>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// cacheable-lookup v7 is ESM-only, while this library currently builds/tests in
|
|
35
|
+
// a CommonJS flow. A cached dynamic import keeps both TypeScript node16 build
|
|
36
|
+
// and runtime compatibility, and shares one lookup cache instance per process.
|
|
37
|
+
const cacheableLookupPromise: Promise<CacheableLookupLike> = import(
|
|
38
|
+
"cacheable-lookup"
|
|
39
|
+
).then(({ default: CacheableLookupClass }) => new CacheableLookupClass());
|
|
40
|
+
|
|
41
|
+
const lookupHostname = async (
|
|
42
|
+
hostname: string,
|
|
43
|
+
family: RestOptions["family"],
|
|
44
|
+
) => {
|
|
45
|
+
const cacheableLookup = await cacheableLookupPromise;
|
|
46
|
+
return family === 0
|
|
47
|
+
? cacheableLookup.lookupAsync(hostname)
|
|
48
|
+
: cacheableLookup.lookupAsync(hostname, { family });
|
|
49
|
+
};
|
|
50
|
+
|
|
23
51
|
/**
|
|
24
52
|
* Type TAssertShape allows to limit json()'s assert callbacks to only those
|
|
25
53
|
* which return an object compatible with TAssertShape.
|
|
@@ -269,9 +297,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
269
297
|
// it in the URL; the hostname will be passed separately via Host header.
|
|
270
298
|
const hostname = url.hostname;
|
|
271
299
|
const headers = new Headers(this.headers);
|
|
272
|
-
const addr = await
|
|
273
|
-
family: this.options.family,
|
|
274
|
-
});
|
|
300
|
+
const addr = await lookupHostname(hostname, this.options.family);
|
|
275
301
|
let redirectMode: RequestRedirect = "follow";
|
|
276
302
|
if (!this.options.allowInternalIPs) {
|
|
277
303
|
const range = parse(addr.address).range();
|
|
@@ -347,6 +373,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
347
373
|
);
|
|
348
374
|
}
|
|
349
375
|
},
|
|
376
|
+
responseEncoding: this.options.responseEncoding,
|
|
350
377
|
});
|
|
351
378
|
}
|
|
352
379
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { RestClient } from "..";
|
|
2
|
+
import substituteParams from "../internal/substituteParams";
|
|
3
|
+
|
|
4
|
+
test("fetch_plain_url", async () => {
|
|
5
|
+
const client = new RestClient();
|
|
6
|
+
const res = await client.get("http://example.com", undefined, "*/*").text();
|
|
7
|
+
expect(res.length).toBeGreaterThan(10);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("fetch_loopback_url_fails", async () => {
|
|
11
|
+
const client = new RestClient();
|
|
12
|
+
await expect(client.get("http://localhost/abc").response()).rejects.toThrow(
|
|
13
|
+
new RegExp(
|
|
14
|
+
"^Domain localhost resolves to a non-public \\(loopback\\) IP address (127\\.0\\.0\\.1)|(\\:\\:1)",
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("fetch_private_url_fails", async () => {
|
|
20
|
+
const client = new RestClient();
|
|
21
|
+
await expect(client.get("http://10.0.0.1/abc").response()).rejects.toThrow(
|
|
22
|
+
"Domain 10.0.0.1 resolves to a non-public (private) IP address 10.0.0.1",
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("fetch_url_with_redirect_fails", async () => {
|
|
27
|
+
const client = new RestClient();
|
|
28
|
+
await expect(client.get("http://google.com").response()).rejects.toThrow(
|
|
29
|
+
/redirect mode is set to error:/,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("fetch_relative_url_fails", async () => {
|
|
34
|
+
const client = new RestClient();
|
|
35
|
+
await expect(client.get("/test").text()).rejects.toThrow(/Invalid URL/);
|
|
36
|
+
await expect(client.get("zzz").text()).rejects.toThrow(/Invalid URL/);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("substituteParams", async () => {
|
|
40
|
+
const body = { aa: "aaa", b: 42, x: "y" };
|
|
41
|
+
expect(substituteParams("/some/:aa/other/:b/ccc/:ddd/eee", body)).toEqual([
|
|
42
|
+
"/some/aaa/other/42/ccc/:ddd/eee",
|
|
43
|
+
{ x: "y" },
|
|
44
|
+
]);
|
|
45
|
+
expect(body).toEqual({ aa: "aaa", b: 42, x: "y" });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("withOptions ignores undefined", async () => {
|
|
49
|
+
const request = new RestClient({ timeoutMs: 1234 })
|
|
50
|
+
.withOptions({ timeoutMs: undefined })
|
|
51
|
+
.writeJson("/", "");
|
|
52
|
+
expect(request.options.timeoutMs).toEqual(1234);
|
|
53
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import delay from "delay";
|
|
2
|
+
import range from "lodash/range";
|
|
3
|
+
import {
|
|
4
|
+
BINARY_BUF,
|
|
5
|
+
consumeIterable,
|
|
6
|
+
createFetchReader,
|
|
7
|
+
server,
|
|
8
|
+
serverAssertConnectionsCount,
|
|
9
|
+
TimeoutError,
|
|
10
|
+
UTF8_BUF,
|
|
11
|
+
} from "./helpers";
|
|
12
|
+
|
|
13
|
+
let ORIGIN: string;
|
|
14
|
+
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
ORIGIN = await new Promise((resolve) => {
|
|
17
|
+
server.listen(0, () =>
|
|
18
|
+
resolve("http://127.0.0.1:" + (server.address() as any).port),
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterAll(async () => {
|
|
24
|
+
await new Promise((resolve) => server.close(resolve));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
beforeEach(async () => {
|
|
28
|
+
await serverAssertConnectionsCount(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("small_response", async () => {
|
|
32
|
+
const reader = createFetchReader(`${ORIGIN}/small`);
|
|
33
|
+
await reader.preload(1024);
|
|
34
|
+
|
|
35
|
+
expect(reader.textFetched).toEqual("ok");
|
|
36
|
+
expect(reader.textIsPartial).toBeFalsy();
|
|
37
|
+
expect(reader.charsRead).toEqual(reader.textFetched.length);
|
|
38
|
+
|
|
39
|
+
for await (const _ of reader) {
|
|
40
|
+
throw "Must not return any more data";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await serverAssertConnectionsCount(0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("large_response", async () => {
|
|
47
|
+
const reader = createFetchReader(`${ORIGIN}/large`);
|
|
48
|
+
await reader.preload(42);
|
|
49
|
+
await serverAssertConnectionsCount(1);
|
|
50
|
+
|
|
51
|
+
expect(reader.textFetched.length).toBeGreaterThan(1);
|
|
52
|
+
expect(reader.textFetched.length).toBeLessThan(1024 * 10);
|
|
53
|
+
expect(reader.textIsPartial).toBeTruthy();
|
|
54
|
+
expect(reader.charsRead).toEqual(reader.textFetched.length);
|
|
55
|
+
|
|
56
|
+
const rest = await consumeIterable(reader);
|
|
57
|
+
await serverAssertConnectionsCount(0);
|
|
58
|
+
expect(rest.length).toBeGreaterThan(1);
|
|
59
|
+
expect(reader.textIsPartial).toBeTruthy();
|
|
60
|
+
expect(reader.charsRead).toEqual(reader.textFetched.length + rest.length);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("slow_response", async () => {
|
|
64
|
+
const reader = createFetchReader(`${ORIGIN}/slow`);
|
|
65
|
+
await reader.preload(42);
|
|
66
|
+
await delay(300);
|
|
67
|
+
await serverAssertConnectionsCount(1);
|
|
68
|
+
await reader[Symbol.asyncIterator]().return();
|
|
69
|
+
await serverAssertConnectionsCount(0);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("small_slow_utf8_response", async () => {
|
|
73
|
+
const reader = createFetchReader(`${ORIGIN}/small_slow_utf8`);
|
|
74
|
+
await reader.preload(1);
|
|
75
|
+
await serverAssertConnectionsCount(1);
|
|
76
|
+
expect(reader.textFetched.length).toEqual(1);
|
|
77
|
+
expect(reader.charsRead).toEqual(1);
|
|
78
|
+
const rest = await consumeIterable(reader);
|
|
79
|
+
expect(reader.textFetched + rest).toEqual(UTF8_BUF.toString());
|
|
80
|
+
await serverAssertConnectionsCount(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("too_big_response", async () => {
|
|
84
|
+
const reader = createFetchReader(`${ORIGIN}/large`, {
|
|
85
|
+
onAfterRead: (reader) => {
|
|
86
|
+
if (reader.charsRead > 1024 * 64) {
|
|
87
|
+
throw Error("too large");
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
await reader.preload(42);
|
|
92
|
+
await serverAssertConnectionsCount(1);
|
|
93
|
+
await expect(async () => consumeIterable(reader, 10)).rejects.toThrow(
|
|
94
|
+
"too large",
|
|
95
|
+
);
|
|
96
|
+
await serverAssertConnectionsCount(0);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("timeout_response_no_preload_long_read_delay", async () => {
|
|
100
|
+
const reader = createFetchReader(`${ORIGIN}/slow`, { timeoutMs: 200 });
|
|
101
|
+
await expect(async () => consumeIterable(reader, 500)).rejects.toThrow(
|
|
102
|
+
TimeoutError,
|
|
103
|
+
);
|
|
104
|
+
await serverAssertConnectionsCount(0);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("timeout_response_no_preload_short_read_delay", async () => {
|
|
108
|
+
const reader = createFetchReader(`${ORIGIN}/slow`, { timeoutMs: 200 });
|
|
109
|
+
await expect(async () => consumeIterable(reader, 10)).rejects.toThrow(
|
|
110
|
+
TimeoutError,
|
|
111
|
+
);
|
|
112
|
+
await serverAssertConnectionsCount(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("timeout_in_preload", async () => {
|
|
116
|
+
const reader = createFetchReader(`${ORIGIN}/slow`, { timeoutMs: 200 });
|
|
117
|
+
await expect(async () => reader.preload(10000000)).rejects.toThrow(
|
|
118
|
+
TimeoutError,
|
|
119
|
+
);
|
|
120
|
+
await serverAssertConnectionsCount(0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("timeout_in_stream_after_preload_succeeded", async () => {
|
|
124
|
+
const reader = createFetchReader(`${ORIGIN}/slow`, { timeoutMs: 200 });
|
|
125
|
+
await reader.preload(42);
|
|
126
|
+
await serverAssertConnectionsCount(1);
|
|
127
|
+
await expect(async () => consumeIterable(reader, 10)).rejects.toThrow(
|
|
128
|
+
TimeoutError,
|
|
129
|
+
);
|
|
130
|
+
await serverAssertConnectionsCount(0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("timeout_after_reader_waited_for_too_long", async () => {
|
|
134
|
+
const reader = createFetchReader(`${ORIGIN}/slow`, { timeoutMs: 200 });
|
|
135
|
+
await reader.preload(42);
|
|
136
|
+
await serverAssertConnectionsCount(1);
|
|
137
|
+
await delay(500);
|
|
138
|
+
await expect(async () => consumeIterable(reader, 10)).rejects.toThrow(
|
|
139
|
+
TimeoutError,
|
|
140
|
+
);
|
|
141
|
+
await serverAssertConnectionsCount(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("read_binary_data", async () => {
|
|
145
|
+
const reader = createFetchReader(`${ORIGIN}/binary`, { timeoutMs: 2000 });
|
|
146
|
+
await reader.preload(42);
|
|
147
|
+
const preload = reader.textFetched;
|
|
148
|
+
expect(reader.textIsPartial).toBeTruthy();
|
|
149
|
+
const data = await consumeIterable(reader, 10);
|
|
150
|
+
expect(Buffer.from(preload + data, "binary")).toEqual(
|
|
151
|
+
Buffer.concat(range(10).map(() => BINARY_BUF)),
|
|
152
|
+
);
|
|
153
|
+
await serverAssertConnectionsCount(0);
|
|
154
|
+
});
|