@clickup/rest-client 2.10.294 → 2.11.0
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 +12 -2
- 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/README.md +2 -0
- package/dist/.eslintcache +1 -1
- package/dist/RestClient.js +2 -2
- package/dist/RestClient.js.map +1 -1
- package/dist/RestOptions.d.ts +3 -0
- 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.map +1 -1
- package/dist/RestRequest.js +1 -0
- package/dist/RestRequest.js.map +1 -1
- package/dist/errors/RestRateLimitError.d.ts.map +1 -1
- package/dist/errors/RestRateLimitError.js.map +1 -1
- package/dist/errors/RestRetriableError.d.ts.map +1 -1
- package/dist/errors/RestRetriableError.js.map +1 -1
- package/dist/errors/RestTokenInvalidError.d.ts.map +1 -1
- package/dist/errors/RestTokenInvalidError.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 +4 -2
- package/dist/internal/RestFetchReader.d.ts.map +1 -1
- package/dist/internal/RestFetchReader.js +4 -4
- package/dist/internal/RestFetchReader.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.js.map +1 -1
- package/dist/internal/inspectPossibleJSON.js +5 -8
- package/dist/internal/inspectPossibleJSON.js.map +1 -1
- package/dist/internal/throwIfErrorResponse.js +1 -1
- package/dist/internal/throwIfErrorResponse.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 +3 -2
- package/dist/middlewares/paceRequests.js.map +1 -1
- package/docs/README.md +2 -0
- package/docs/classes/RestClient.md +32 -28
- package/docs/classes/RestContentSizeOverLimitError.md +5 -1
- package/docs/classes/RestError.md +5 -1
- package/docs/classes/RestRateLimitError.md +6 -2
- package/docs/classes/RestRequest.md +22 -18
- package/docs/classes/RestResponse.md +7 -3
- package/docs/classes/RestResponseError.md +5 -1
- package/docs/classes/RestRetriableError.md +6 -2
- package/docs/classes/RestStream.md +12 -8
- package/docs/classes/RestTimeoutError.md +5 -1
- package/docs/classes/RestTokenInvalidError.md +6 -2
- package/docs/interfaces/Middleware.md +4 -4
- package/docs/interfaces/Pacer.md +7 -12
- package/docs/interfaces/PacerOutcome.md +25 -0
- package/docs/interfaces/RestLogEvent.md +1 -1
- package/docs/interfaces/RestOptions.md +42 -30
- package/docs/interfaces/TokenGetter.md +3 -3
- package/docs/modules.md +8 -11
- 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 +13 -0
- package/jest.config.js +1 -10
- package/package.json +10 -6
- package/src/RestClient.ts +21 -21
- package/src/RestOptions.ts +6 -3
- package/src/RestRequest.ts +12 -11
- package/src/RestResponse.ts +1 -1
- package/src/RestStream.ts +1 -1
- package/src/__tests__/RestClient.test.ts +53 -0
- package/src/__tests__/RestFetchReader.test.ts +150 -0
- package/src/__tests__/RestRequest.test.ts +262 -0
- package/src/__tests__/RestStream.test.ts +63 -0
- package/src/__tests__/helpers.ts +173 -0
- package/src/errors/RestRateLimitError.ts +5 -1
- package/src/errors/RestResponseError.ts +3 -3
- package/src/errors/RestRetriableError.ts +5 -1
- package/src/errors/RestTokenInvalidError.ts +4 -1
- package/src/helpers/depaginate.ts +3 -3
- package/src/index.ts +2 -7
- package/src/internal/RestFetchReader.ts +6 -3
- package/src/internal/RestRangeUploader.ts +2 -2
- package/src/internal/calcRetryDelay.ts +2 -2
- package/src/internal/ellipsis.ts +16 -0
- package/src/internal/inferResBodyEncoding.ts +13 -13
- package/src/internal/inspectPossibleJSON.ts +6 -10
- package/src/internal/substituteParams.ts +1 -1
- package/src/internal/throwIfErrorResponse.ts +8 -8
- package/src/middlewares/paceRequests.ts +28 -3
- package/tsconfig.base.json +31 -0
- package/tsconfig.json +1 -32
- 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 -62
- package/docs/classes/PacerQPS.md +0 -75
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[@clickup/rest-client](../README.md) / [Exports](../modules.md) / TokenGetter
|
|
2
2
|
|
|
3
|
-
# Interface: TokenGetter
|
|
3
|
+
# Interface: TokenGetter\<TData\>
|
|
4
4
|
|
|
5
5
|
A callback which returns access token, possibly after refreshing it, and also
|
|
6
6
|
possibly before a retry on "invalid token" condition. I.e. it can be called
|
|
@@ -17,7 +17,7 @@ will be passed as a parameter).
|
|
|
17
17
|
|
|
18
18
|
### TokenGetter
|
|
19
19
|
|
|
20
|
-
▸ **TokenGetter**(`prevError`): `Promise
|
|
20
|
+
▸ **TokenGetter**(`prevError`): `Promise`\<`TData`\>
|
|
21
21
|
|
|
22
22
|
#### Parameters
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ will be passed as a parameter).
|
|
|
27
27
|
|
|
28
28
|
#### Returns
|
|
29
29
|
|
|
30
|
-
`Promise
|
|
30
|
+
`Promise`\<`TData`\>
|
|
31
31
|
|
|
32
32
|
#### Defined in
|
|
33
33
|
|
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,16 +22,14 @@
|
|
|
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
|
|
|
34
30
|
### depaginate
|
|
35
31
|
|
|
36
|
-
▸ **depaginate
|
|
32
|
+
▸ **depaginate**\<`TItem`, `TCursor`\>(`readFunc`): `AsyncGenerator`\<`TItem`, `void`, `undefined`\>
|
|
37
33
|
|
|
38
34
|
Keeps calling a function with an updating cursor, and depaginates all the
|
|
39
35
|
results until the cursor returned is null or undefined.
|
|
@@ -53,11 +49,11 @@ On each call, the inner function needs to return an array with two elements:
|
|
|
53
49
|
|
|
54
50
|
| Name | Type |
|
|
55
51
|
| :------ | :------ |
|
|
56
|
-
| `readFunc` | (`cursor`: `undefined` \| `TCursor`) => `Promise
|
|
52
|
+
| `readFunc` | (`cursor`: `undefined` \| `TCursor`) => `Promise`\<readonly [`TItem`[], `undefined` \| ``null`` \| `TCursor`]\> |
|
|
57
53
|
|
|
58
54
|
#### Returns
|
|
59
55
|
|
|
60
|
-
`AsyncGenerator
|
|
56
|
+
`AsyncGenerator`\<`TItem`, `void`, `undefined`\>
|
|
61
57
|
|
|
62
58
|
#### Defined in
|
|
63
59
|
|
|
@@ -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.
|
|
@@ -76,7 +72,8 @@ Pacer implementations.
|
|
|
76
72
|
|
|
77
73
|
| Name | Type |
|
|
78
74
|
| :------ | :------ |
|
|
79
|
-
| `pacer` | ``null`` \| [`Pacer`](interfaces/Pacer.md) \| (`req`: [`RestRequest`](classes/RestRequest.md)
|
|
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,13 @@
|
|
|
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
|
+
: { forceExit: true, testTimeout: 30000 }),
|
|
10
|
+
transform: {
|
|
11
|
+
"\\.ts$": "ts-jest",
|
|
12
|
+
},
|
|
13
|
+
};
|
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.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"rest-client",
|
|
@@ -24,18 +24,18 @@
|
|
|
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
37
|
"delay": "^4.4.1",
|
|
38
|
-
"fast-typescript-memoize": "^1.
|
|
38
|
+
"fast-typescript-memoize": "^1.1.1",
|
|
39
39
|
"ipaddr.js": "^1.9.1",
|
|
40
40
|
"lodash": "^4.17.21",
|
|
41
41
|
"node-fetch": "^2.6.11",
|
|
@@ -45,11 +45,13 @@
|
|
|
45
45
|
"@types/jest": "^29.5.5",
|
|
46
46
|
"@types/lodash": "^4.14.175",
|
|
47
47
|
"@types/node-fetch": "^2.6.4",
|
|
48
|
+
"@types/node": "^20.4.1",
|
|
48
49
|
"@typescript-eslint/eslint-plugin": "^5.59.6",
|
|
49
50
|
"@typescript-eslint/parser": "^5.59.6",
|
|
50
51
|
"eslint-import-resolver-typescript": "^3.5.5",
|
|
51
52
|
"eslint-plugin-import": "^2.27.5",
|
|
52
53
|
"eslint-plugin-lodash": "^7.4.0",
|
|
54
|
+
"eslint-plugin-no-only-tests": "^3.1.0",
|
|
53
55
|
"eslint-plugin-node": "^11.1.0",
|
|
54
56
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
55
57
|
"eslint-plugin-react": "^7.32.2",
|
|
@@ -57,6 +59,8 @@
|
|
|
57
59
|
"eslint-plugin-typescript-sort-keys": "^2.3.0",
|
|
58
60
|
"eslint-plugin-unused-imports": "^2.0.0",
|
|
59
61
|
"eslint": "^8.40.0",
|
|
62
|
+
"jest": "^29.7.0",
|
|
63
|
+
"prettier": "3.2.1",
|
|
60
64
|
"superstruct": "^1.0.3",
|
|
61
65
|
"ts-jest": "^29.1.1",
|
|
62
66
|
"typedoc-plugin-markdown": "^3.16.0",
|
package/src/RestClient.ts
CHANGED
|
@@ -54,7 +54,7 @@ export default class RestClient {
|
|
|
54
54
|
*/
|
|
55
55
|
withMiddleware(
|
|
56
56
|
middleware: RestOptions["middlewares"][0],
|
|
57
|
-
method: "unshift" | "push" = "push"
|
|
57
|
+
method: "unshift" | "push" = "push",
|
|
58
58
|
) {
|
|
59
59
|
const clone = new RestClient(this._options);
|
|
60
60
|
clone._options.middlewares[method](middleware);
|
|
@@ -156,7 +156,7 @@ export default class RestClient {
|
|
|
156
156
|
*/
|
|
157
157
|
withOAuth1(
|
|
158
158
|
consumer: { consumerKey: string; consumerSecret: string },
|
|
159
|
-
token: TokenGetter<{ token: string; tokenSecret: string }
|
|
159
|
+
token: TokenGetter<{ token: string; tokenSecret: string }>,
|
|
160
160
|
) {
|
|
161
161
|
const oauth = new OAuth1({
|
|
162
162
|
consumer: { key: consumer.consumerKey, secret: consumer.consumerSecret },
|
|
@@ -176,7 +176,7 @@ export default class RestClient {
|
|
|
176
176
|
oauth.authorize(requestData, {
|
|
177
177
|
key: tokenData.token,
|
|
178
178
|
secret: tokenData.tokenSecret,
|
|
179
|
-
})
|
|
179
|
+
}),
|
|
180
180
|
);
|
|
181
181
|
for (const [name, value] of Object.entries(addHeaders)) {
|
|
182
182
|
req.headers.set(name, value);
|
|
@@ -196,7 +196,7 @@ export default class RestClient {
|
|
|
196
196
|
const unencodedHeader = name + ":" + password;
|
|
197
197
|
req.headers.set(
|
|
198
198
|
"Authorization",
|
|
199
|
-
"Basic " + Buffer.from(unencodedHeader).toString("base64")
|
|
199
|
+
"Basic " + Buffer.from(unencodedHeader).toString("base64"),
|
|
200
200
|
);
|
|
201
201
|
return next(req);
|
|
202
202
|
});
|
|
@@ -211,7 +211,7 @@ export default class RestClient {
|
|
|
211
211
|
get(
|
|
212
212
|
path: string,
|
|
213
213
|
args: Partial<Record<string, string | number | string[]>> = {},
|
|
214
|
-
accept: string = "application/json"
|
|
214
|
+
accept: string = "application/json",
|
|
215
215
|
) {
|
|
216
216
|
return this._noBodyRequest(path, args, "GET", accept);
|
|
217
217
|
}
|
|
@@ -224,7 +224,7 @@ export default class RestClient {
|
|
|
224
224
|
body: string | Buffer | NodeJS.ReadableStream,
|
|
225
225
|
contentType: string,
|
|
226
226
|
method: "POST" | "PUT" | "PATCH" = "POST",
|
|
227
|
-
accept?: string
|
|
227
|
+
accept?: string,
|
|
228
228
|
) {
|
|
229
229
|
const origShape = simpleShape(path);
|
|
230
230
|
return new RestRequest(
|
|
@@ -236,7 +236,7 @@ export default class RestClient {
|
|
|
236
236
|
"Content-Type": contentType,
|
|
237
237
|
}),
|
|
238
238
|
body,
|
|
239
|
-
origShape
|
|
239
|
+
origShape,
|
|
240
240
|
);
|
|
241
241
|
}
|
|
242
242
|
|
|
@@ -247,7 +247,7 @@ export default class RestClient {
|
|
|
247
247
|
path: string,
|
|
248
248
|
body: any,
|
|
249
249
|
method: "POST" | "PUT" | "PATCH" | "DELETE" = "POST",
|
|
250
|
-
accept: string = "application/json"
|
|
250
|
+
accept: string = "application/json",
|
|
251
251
|
) {
|
|
252
252
|
const origShape = simpleShape(path, body);
|
|
253
253
|
[path, body] = substituteParams(path, body);
|
|
@@ -260,7 +260,7 @@ export default class RestClient {
|
|
|
260
260
|
"Content-Type": "application/json",
|
|
261
261
|
}),
|
|
262
262
|
JSON.stringify(body),
|
|
263
|
-
origShape
|
|
263
|
+
origShape,
|
|
264
264
|
);
|
|
265
265
|
}
|
|
266
266
|
|
|
@@ -271,7 +271,7 @@ export default class RestClient {
|
|
|
271
271
|
path: string,
|
|
272
272
|
body: Partial<Record<string, string>> | string,
|
|
273
273
|
method: "POST" | "PUT" | "PATCH" = "POST",
|
|
274
|
-
accept: string = "application/json"
|
|
274
|
+
accept: string = "application/json",
|
|
275
275
|
) {
|
|
276
276
|
const origShape = simpleShape(path, body);
|
|
277
277
|
[path, body] = substituteParams(path, body);
|
|
@@ -286,9 +286,9 @@ export default class RestClient {
|
|
|
286
286
|
typeof body === "string"
|
|
287
287
|
? body
|
|
288
288
|
: new URLSearchParams(
|
|
289
|
-
omitBy(body, isUndefined) as Record<string, string
|
|
289
|
+
omitBy(body, isUndefined) as Record<string, string>,
|
|
290
290
|
).toString(),
|
|
291
|
-
origShape
|
|
291
|
+
origShape,
|
|
292
292
|
);
|
|
293
293
|
}
|
|
294
294
|
|
|
@@ -298,7 +298,7 @@ export default class RestClient {
|
|
|
298
298
|
writeDelete(
|
|
299
299
|
path: string,
|
|
300
300
|
args: Partial<Record<string, string>> = {},
|
|
301
|
-
accept: string = "application/json"
|
|
301
|
+
accept: string = "application/json",
|
|
302
302
|
) {
|
|
303
303
|
return this._noBodyRequest(path, args, "DELETE", accept);
|
|
304
304
|
}
|
|
@@ -355,14 +355,14 @@ export default class RestClient {
|
|
|
355
355
|
mimeType: string,
|
|
356
356
|
stream: AsyncIterable<Buffer>,
|
|
357
357
|
method: "POST" | "PUT" = "POST",
|
|
358
|
-
chunkSize: number
|
|
358
|
+
chunkSize: number,
|
|
359
359
|
) {
|
|
360
360
|
return new RestRangeUploader(
|
|
361
361
|
this,
|
|
362
362
|
chunkSize,
|
|
363
363
|
method,
|
|
364
364
|
path,
|
|
365
|
-
mimeType
|
|
365
|
+
mimeType,
|
|
366
366
|
).upload(stream);
|
|
367
367
|
}
|
|
368
368
|
|
|
@@ -381,7 +381,7 @@ export default class RestClient {
|
|
|
381
381
|
"",
|
|
382
382
|
new Headers({ "Content-Type": "application/json" }),
|
|
383
383
|
JSON.stringify({ variables, query }), // variables first - for truncation in logs,
|
|
384
|
-
origShape
|
|
384
|
+
origShape,
|
|
385
385
|
);
|
|
386
386
|
}
|
|
387
387
|
|
|
@@ -392,7 +392,7 @@ export default class RestClient {
|
|
|
392
392
|
path: string,
|
|
393
393
|
args: Partial<Record<string, string | number | string[]>> = {},
|
|
394
394
|
method: "GET" | "DELETE",
|
|
395
|
-
accept: string = "application/json"
|
|
395
|
+
accept: string = "application/json",
|
|
396
396
|
) {
|
|
397
397
|
const origShape = simpleShape(path, args);
|
|
398
398
|
[path, args] = substituteParams(path, args);
|
|
@@ -418,7 +418,7 @@ export default class RestClient {
|
|
|
418
418
|
path,
|
|
419
419
|
new Headers({ Accept: accept }),
|
|
420
420
|
"",
|
|
421
|
-
origShape
|
|
421
|
+
origShape,
|
|
422
422
|
);
|
|
423
423
|
}
|
|
424
424
|
}
|
|
@@ -431,7 +431,7 @@ export default class RestClient {
|
|
|
431
431
|
*/
|
|
432
432
|
export async function tokenRetryStrategy<TData>(
|
|
433
433
|
token: TokenGetter<TData>,
|
|
434
|
-
body: (tokenData: TData) => Promise<RestResponse
|
|
434
|
+
body: (tokenData: TData) => Promise<RestResponse>,
|
|
435
435
|
) {
|
|
436
436
|
let tokenData = await token(null);
|
|
437
437
|
try {
|
|
@@ -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
|
@@ -27,7 +27,7 @@ export interface RestLogEvent {
|
|
|
27
27
|
export interface Middleware {
|
|
28
28
|
(
|
|
29
29
|
req: RestRequest,
|
|
30
|
-
next: (req: RestRequest) => Promise<RestResponse
|
|
30
|
+
next: (req: RestRequest) => Promise<RestResponse>,
|
|
31
31
|
): Promise<RestResponse>;
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -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. */
|
|
@@ -140,7 +142,7 @@ export default interface RestOptions {
|
|
|
140
142
|
* isRateLimitError and isRetriableError handlers set up, and they return
|
|
141
143
|
* contradictory information; then isRateLimitError wins. */
|
|
142
144
|
isRateLimitError: (
|
|
143
|
-
res: RestResponse
|
|
145
|
+
res: RestResponse,
|
|
144
146
|
) => "SOMETHING_ELSE" | "RATE_LIMIT" | "BEST_EFFORT" | number;
|
|
145
147
|
/** Decides whether the response is a token-invalid error or not. In case it's
|
|
146
148
|
* not, the response ought to be either success or some other error. */
|
|
@@ -154,7 +156,7 @@ export default interface RestOptions {
|
|
|
154
156
|
* retry will happen in not less than this number of milliseconds. */
|
|
155
157
|
isRetriableError: (
|
|
156
158
|
res: RestResponse,
|
|
157
|
-
_error: any
|
|
159
|
+
_error: any,
|
|
158
160
|
) => "NEVER_RETRY" | "RETRY" | "BEST_EFFORT" | number;
|
|
159
161
|
}
|
|
160
162
|
|
|
@@ -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
|
@@ -33,7 +33,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
33
33
|
public url: string,
|
|
34
34
|
public readonly headers: Headers,
|
|
35
35
|
public readonly body: string | Buffer | NodeJS.ReadableStream,
|
|
36
|
-
public readonly shape?: string
|
|
36
|
+
public readonly shape?: string,
|
|
37
37
|
) {
|
|
38
38
|
this.options = { ...options };
|
|
39
39
|
}
|
|
@@ -120,7 +120,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
120
120
|
// By passing Number.MAX_SAFE_INTEGER to stream(), we ensure that the
|
|
121
121
|
// entire data will be preloaded, or the loading will fail due to
|
|
122
122
|
// throwIfResIsBigger limitation, whichever will happen faster.
|
|
123
|
-
Number.MAX_SAFE_INTEGER
|
|
123
|
+
Number.MAX_SAFE_INTEGER,
|
|
124
124
|
);
|
|
125
125
|
await stream.close();
|
|
126
126
|
return stream.res;
|
|
@@ -182,7 +182,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
182
182
|
await reader?.close();
|
|
183
183
|
throw e;
|
|
184
184
|
}
|
|
185
|
-
}
|
|
185
|
+
},
|
|
186
186
|
);
|
|
187
187
|
|
|
188
188
|
// The only place where we return the response. Otherwise we retry or
|
|
@@ -215,7 +215,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
215
215
|
error,
|
|
216
216
|
this.options,
|
|
217
217
|
res,
|
|
218
|
-
retryDelayMs
|
|
218
|
+
retryDelayMs,
|
|
219
219
|
);
|
|
220
220
|
if (newRetryDelay === "no_retry") {
|
|
221
221
|
throw error;
|
|
@@ -228,7 +228,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
228
228
|
retryDelayMs *= random(
|
|
229
229
|
1 - this.options.retryDelayJitter,
|
|
230
230
|
1 + this.options.retryDelayJitter,
|
|
231
|
-
true
|
|
231
|
+
true,
|
|
232
232
|
);
|
|
233
233
|
await this.options.heartbeater.delay(retryDelayMs);
|
|
234
234
|
|
|
@@ -279,7 +279,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
279
279
|
const isInternal = range !== "unicast";
|
|
280
280
|
if (isInternal) {
|
|
281
281
|
throw new RestError(
|
|
282
|
-
`Domain ${hostname} resolves to a non-public (${range}) IP address ${addr.address}
|
|
282
|
+
`Domain ${hostname} resolves to a non-public (${range}) IP address ${addr.address}`,
|
|
283
283
|
);
|
|
284
284
|
}
|
|
285
285
|
|
|
@@ -333,7 +333,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
333
333
|
onTimeout: (reader) => {
|
|
334
334
|
throw new RestTimeoutError(
|
|
335
335
|
`Timed out while reading response body (${this.options.timeoutMs} ms)`,
|
|
336
|
-
this._createRestResponse(reader)
|
|
336
|
+
this._createRestResponse(reader)!,
|
|
337
337
|
);
|
|
338
338
|
},
|
|
339
339
|
onAfterRead: (reader) => {
|
|
@@ -343,10 +343,11 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
343
343
|
) {
|
|
344
344
|
throw new RestContentSizeOverLimitError(
|
|
345
345
|
`Content size is over limit of ${this.options.throwIfResIsBigger} characters`,
|
|
346
|
-
this._createRestResponse(reader)
|
|
346
|
+
this._createRestResponse(reader)!,
|
|
347
347
|
);
|
|
348
348
|
}
|
|
349
349
|
},
|
|
350
|
+
responseEncoding: this.options.responseEncoding,
|
|
350
351
|
});
|
|
351
352
|
}
|
|
352
353
|
|
|
@@ -361,7 +362,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
361
362
|
reader.status,
|
|
362
363
|
reader.headers,
|
|
363
364
|
reader.textFetched,
|
|
364
|
-
reader.textIsPartial
|
|
365
|
+
reader.textIsPartial,
|
|
365
366
|
);
|
|
366
367
|
}
|
|
367
368
|
|
|
@@ -405,7 +406,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
405
406
|
inspectPossibleJSON(
|
|
406
407
|
event.res.headers,
|
|
407
408
|
event.res.text,
|
|
408
|
-
MAX_DEBUG_LEN
|
|
409
|
+
MAX_DEBUG_LEN,
|
|
409
410
|
);
|
|
410
411
|
}
|
|
411
412
|
} else if (event.exception) {
|
|
@@ -430,7 +431,7 @@ export default class RestRequest<TAssertShape = any> {
|
|
|
430
431
|
async function runMiddlewares(
|
|
431
432
|
req: RestRequest,
|
|
432
433
|
middlewares: RestOptions["middlewares"],
|
|
433
|
-
last: (req: RestRequest) => Promise<RestResponse
|
|
434
|
+
last: (req: RestRequest) => Promise<RestResponse>,
|
|
434
435
|
): Promise<RestResponse> {
|
|
435
436
|
if (middlewares.length > 0) {
|
|
436
437
|
const [head, ...tail] = middlewares;
|
package/src/RestResponse.ts
CHANGED
package/src/RestStream.ts
CHANGED
|
@@ -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
|
+
});
|